0

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.

immediately after replacing array

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

one second after replacing array

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

interval continues to refresh

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.

Mike Feltman
  • 5,160
  • 1
  • 17
  • 38
Ian
  • 161
  • 1
  • 17
  • 1
    Just a hunch, but try adding track by $index to your ng-repeat. Also, I think you're interface will appear much less jumpy and cleaner if you actually update the array with each iteration rather than overwriting all of the values. – Mike Feltman Aug 01 '17 at 16:00
  • 2
    So why are you using `$rootScope` ? I think using `$scope` simply will make your code more readable and manageable. Also, what is `dataService` and what does it do ? – M0nst3R Aug 01 '17 at 16:02
  • Mike Feltman you are the winner! This absolutely solved my issue. Thanks a million. I think you should post this as an answer to this question and I'll mark it as such. – Ian Aug 01 '17 at 16:08
  • Awesome. I see you already added it as an answer. – Mike Feltman Aug 01 '17 at 17:17

3 Answers3

2

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

Community
  • 1
  • 1
georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • thanks for the heads up. updated all my 'track by' statements to use the unique ID on the object. works well so far. – Ian Aug 01 '17 at 20:17
1

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.

Ian
  • 161
  • 1
  • 17
0

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.

Mike Feltman
  • 5,160
  • 1
  • 17
  • 38