Angular: Updating $scope with new data causes old

2019-08-17 23:56发布

问题:

I'm having a strange issue where I replace the values in $rootScope.data.vehicles with new data but the old data points on my view remain for about one second alongside the new data.

For instance, right after I replace the $rootScope.data.vehicles array with a new array my view will display the 3 new values first followed by the 3 old values. View is using ng-repeat on $rootScope.data.vehicles to display tiles.

About one second later the 3 old values are no longer displayed in the view.

Each time the interval fires I get new values followed by old values, even if the values have not changed.

My controller and factory look like so:

(function () {
    var vehiclesInjectParams = ['$location', 'dataService', '$rootScope', 'VehiclesRefreshService'];

    var VehiclesController = function ($location, dataService, $rootScope, VehiclesRefreshService) {
        var vm = this;

        if ($rootScope.data == undefined)
            $rootScope.data = {};

        $rootScope.data.vehicles = [];

        function init() {
            dataService.getVehicles()
                .then(function (data) {
                    $rootScope.data.vehicles = data.results;
                }, function (error) {
                    var thisError = error.data.message;
                });

            VehiclesRefreshService.getValues();
        };

        init();
    };

    VehiclesController.$inject = vehiclesInjectParams;

    var vehiclesRefreshInjectParams = ['$interval', '$rootScope', '$q', 'dataService'];

    var VehiclesRefreshService = function ($interval, $rootScope, $q, dataService) {
        var factory = {};

        factory.getValues = function () {
            var interval = $interval(function () {
                dataService.getVehicles()
                .then(function (data) {
                    $rootScope.data.vehicles = data.results;
                }, function (error) {
                    var thisError = error.data.message;
                });
            }, 10000);

        };

        return factory;
    };

    VehiclesRefreshService.$inject = vehiclesRefreshInjectParams;

    angular.module('teleAiDiagnostics').controller('VehiclesController', VehiclesController).factory('VehiclesRefreshService', VehiclesRefreshService);
}());

First I load the array then I start an $interval timer to refresh the values every 10 seconds. As soon as the dataService returns the new list of values and puts them in $rootScope.data.vehicles I notice the six tiles instead of 3. One second after that I'm left with only the 3 new tiles.

My view looks like so:

<header>
    <h1>Vehicles</h1>
</header>

<article class="icon-gallery flexslider">
    <section>
        <figure ng-repeat="vehicle in $parent.data.vehicles" class="gallery-item svg">
            <a class="nextpage" ng-href="#!/vehicle/{{vehicle.vehicleID}}">
                <img src="img/machine/01.svg" alt="">
                <span class="green-machine-code">{{vehicle.id}}</span>
            </a>
            <ul>
                <li>{{vehicle.id}}</li>
                <li>{{vehicle.ip}}</li>
                <li>Online: {{vehicle.online}}</li>
                <li>Status: {{vehicle.status}}</li>
                <li>AllClear: {{vehicle.allClear}}</li>
            </ul>
        </figure>
    </section>
</article>

Any ideas as to how to get my tiles to refresh seamlessly? All help is greatly appreciated.

回答1:

Avoid track by $index when there is a unique property identifier to work with.

<figure ng-repeat="vehicle in $parent.data.vehicles track by ̲v̲e̲h̲i̲c̲l̲e̲.̲i̲d̲ ̶$̶i̶n̶d̶e̶x̶" class="gallery-item svg">
    <a class="nextpage" ng-href="#!/vehicle/{{vehicle.vehicleID}}">
        <img src="img/machine/01.svg" alt="">
        <span class="green-machine-code">{{vehicle.id}}</span>
    </a>
    <ul>
        <li>{{vehicle.id}}</li>
        <li>{{vehicle.ip}}</li>
        <li>Online: {{vehicle.online}}</li>
        <li>Status: {{vehicle.status}}</li>
        <li>AllClear: {{vehicle.allClear}}</li>
    </ul>
</figure>

From the Docs:

If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance. Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.

— AngularJS ng-repeat Directive API Reference - Tracking



回答2:

Mike Feltman in the comments above resolved the issue. It was as simple as adding 'track by $index' to the ng-repeat tag in the view. See here:

<header>
    <h1>Vehicles</h1>
</header>

<article class="icon-gallery flexslider">
    <section>
        <figure ng-repeat="vehicle in $parent.data.vehicles track by $index" class="gallery-item svg">
            <a class="nextpage" ng-href="#!/vehicle/{{vehicle.vehicleID}}">
                <img src="img/machine/01.svg" alt="">
                <span class="green-machine-code">{{vehicle.id}}</span>
            </a>
            <ul>
                <li>{{vehicle.id}}</li>
                <li>{{vehicle.ip}}</li>
                <li>Online: {{vehicle.online}}</li>
                <li>Status: {{vehicle.status}}</li>
                <li>AllClear: {{vehicle.allClear}}</li>
            </ul>
        </figure>
    </section>
</article>

Hopefully this post will help someone in the future who is experiencing the same type of problem.



回答3:

IIRC, internally Angular sees that the vehicles array has changed and immediately renders it, then cleans up. Adding track by $index to the ng-repeat should basically force rendering to start at the first item each time.

I do agree with the other post regarding the use of $rootScope. If you're trying to make the vehicles available globally rather than storing them in rootScope, store them in your data service and inject it whereever necessary. It will be a much cleaner approach.