Angular version is 1.4.7.
The model in question contains two objects, 'systems', an array, and 'selectedSystem'. What I want is for selectedSystem to reference one of the objects in systems. This is the case when the page loads, and everything works as expected, but when I make a selection from the first dropdown, selectedSystem seems to become a copy rather than a reference to the original object in systems. Consequently, changes to the second drop down no longer are reflected in systems.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Hello</title>
</head>
<body ng-app="testApp">
Hi There!
<div ng-controller="TestAppSummaryCtrl">
<input type="button" value="Add Query" ng-click="addQuery()"/>
<select ng-model="state.selectedSystem" ng-options="system.description for system in state.systems track by system.systemId" ></select>
<select ng-model="state.selectedSystem.currentEnvironment" ng-options="environment.description for environment in state.selectedSystem.environments track by environment.environmentId"></select>
Selected System: {{state.selectedSystem.systemId}}
<div ng-repeat="item in state.systems">
System: {{item.description}}
Current Environment: {{item.currentEnvironment.description}}
</div>
<div ng-repeat="item in state.selectedSystem.categories">
Cateogry:
{{item.categoryId}}
{{item.description}}
<br />
Queries:
<div ng-repeat="query in item.queries">
{{query.queryId}}
{{query.latestStatus}}
</div>
</div>
</div>
<script src="../Scripts/angular.js"></script>
<script src="../Scripts/angular-route.js"></script>
<script src="src/test.js"></script>
</body>
</html>
typescript code:
/// <reference path="../../scripts/typings/angularjs/angular.d.ts" />
/// <reference path="../../scripts/typings/angularjs/angular-route.d.ts" />
module TestApp {
export class Config {
constructor($routeProvider: ng.route.IRouteProvider) {
$routeProvider.when("/test", {
templateUrl: "StaticContent/StaticTest.html",
controller: "TestAppCtrl"
});
}
}
Config.$inject = ['$routeProvider'];
export class SummaryService {
private summaryApiPath: string;
private httpService: ng.IHttpService;
private qService: ng.IQService;
private systems: Array<Extensions.SystemSummary>;
constructor($http: ng.IHttpService, $q: ng.IQService) {
this.summaryApiPath = "../api/systemList";
this.httpService = $http;
this.qService = $q;
}
getSystems(): ng.IPromise<any> {
if (this.systems != undefined) {
return this.qService.when(this.systems);
}
var deferred = this.qService.defer();
this.httpService.get(this.summaryApiPath).then((result: any) => {
deferred.resolve(result.data);
}), error => {
deferred.reject(error);
}
return deferred.promise;
}
public static serviceFactory($http: ng.IHttpService, $q: ng.IQService): SummaryService {
return new SummaryService($http, $q);
}
}
export class TestAppSummaryCtrl {
private $scope: Extensions.ISummaryScope
private summaryService: SummaryService;
private init(): void {
var local = this.$scope;
this.summaryService.getSystems().then(data => {
local.state.systems = <Array<Extensions.SystemSummary>>data;
local.state.selectedSystem = local.state.systems.length == 0 ? undefined : local.state.systems[0];
});
local.updateCurrentEnvironment = envId => local.state.selectedSystem.currentEnvironment = local.state.selectedSystem.environments[envId];
}
constructor($scope: Extensions.ISummaryScope, summaryService: SummaryService) {
this.$scope = $scope;
this.$scope.state = new Extensions.SummaryCtrlUIState();
this.summaryService = summaryService;
this.init();
}
}
TestAppSummaryCtrl.$inject = ['$scope', 'summaryService'];
var app = angular.module('testApp', ['ngRoute']);
app.config(Config);
app.factory('summaryService', ['$http', '$q', SummaryService.serviceFactory]);
app.controller('TestAppSummaryCtrl', TestAppSummaryCtrl);
}
module Extensions {
export class CategorySummary {
categoryId: number;
description: number;
queries: Array<JobItemSummary>;
}
export class JobItemSummary {
queryId: number;
lastJobId: number;
lastCompletedDate: string;
latestStatus: string;
latestResultsCount: number;
latestResultsSummary: string;
expectedResult: number;
}
export class EnvironmentSummary {
environmentId: number;
description: string;
}
export class SystemSummary {
systemId: number;
description: string;
environments: Array<EnvironmentSummary>;
currentEnvironment: EnvironmentSummary;
categories: Array<CategorySummary>;
}
export class SummaryCtrlUIState {
selectedSystem: Extensions.SystemSummary;
systems: Array<Extensions.SystemSummary>;
}
export interface ISummaryScope extends ng.IScope {
state: SummaryCtrlUIState;
updateCurrentEnvironment(envId: number): void;
addQuery(): void;
}
}
What is going on, and Is there some way to get the behavior I want with angular model binding?
Greetings from the future.
Having a similar issue as you, and since this is over a year old, I figured I'd do some digging. This seems to be by design, even in v1.6. Lines 399-403 of ngOptions has this interesting tidbit:
getViewValueFromOption: function(option) {
// If the viewValue could be an object that may be mutated by the application,
// we need to make a copy and not return the reference to the value on the option.
return trackBy ? copy(option.viewValue) : option.viewValue;
}
So yeah, track by is making a copy of the value to your ngModel. For now I think the only recourse would be to write your own logic to track selections and/or send changes back to the originating array.
edit: I've opened an issue on the angular.js github repo: https://github.com/angular/angular.js/issues/15980
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With