Búsqueda de sitios web

angularjs - La vinculación de selección de Angular rompe referencias en el modelo


La versión de Angular es 1.4.7.

El modelo en cuestión contiene dos objetos, 'systems', un arreglo, y 'selectedSystem'. Lo que quiero es que selectedSystem haga referencia a uno de los objetos en systems. Este es el caso cuando la página se carga, y todo funciona como se espera, pero cuando hago una selección del primer menú desplegable, selectedSystem parece convertirse en una copia en lugar de una referencia al objeto original en systems. En consecuencia, los cambios en el segundo menú desplegable ya no se reflejan en 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>

código de TypeScript:

/// <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; 
    }
}

¿Qué está pasando, y hay alguna manera de obtener el comportamiento que quiero con el enlace de modelo de Angular?

Respuesta #1

Saludos desde el futuro.

Teniendo un problema similar al tuyo, y dado que esto tiene más de un año, pensé en investigar un poco. Esto parece ser intencional, incluso en la v1.6. Las líneas 399-403 de ngOptions tienen este dato interesante:

        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;
        }

Así que sí, track by está haciendo una copia del valor a tu ngModel. Por ahora creo que el único recurso sería escribir tu propia lógica para rastrear las selecciones y/o enviar cambios de vuelta al arreglo original.

editar: He abierto un problema en el repositorio de angular.js en GitHub: https://github.com/angular/angular.js/issues/15980