1

I updated my array from service but view wasn't updated.

And when I call $scope.$apply() or $scope.$digest, it logs

$digest already in progress

This is my main.html file. ctrl.flightsTo is the array I mentioned

<md-progress-linear class="md-accent" ng-hide="ctrl.isFlightDataLoaded" mode="indeterminate"></md-progress-linear>
<div class="md-padding">
  <md-autocomplete
      ng-show="ctrl.isFlightDataLoaded"
      md-selected-item="ctrl.selectedItem"
      md-selected-item-change="ctrl.selectedItemChange(item)"
      md-search-text="ctrl.searchText"
      md-items="item in ctrl.querySearch(searchText)"
      md-item-text="item.name"
      md-min-length="0"
      placeholder="Choose a destination">
    <md-item-template>
      <span md-highlight-text="ctrl.searchText">{{item.name}} - {{item.iata}}</span>
    </md-item-template>
  </md-autocomplete>
  <ul>
    <li ng-repeat="flight in ctrl.flightsTo">{{flight}}</li>
  </ul>
</div>

And this is my MainCtrl.

When md-selected-item-change event is called, I get my flight data from flightDataService in selectedItemChange function.

But Angular doesn't update views.

.controller('MainCtrl', function(flightDataService) {
    var self = this;
    self.isFlightDataLoaded = false;
    self.destinations = [];
    self.querySearch = querySearch;
    self.flightsTo = [];
    self.selectedItemChange = selectedItemChange;
    self.init = init;

    function querySearch(query) {
      var results = query ? self.destinations.filter(createFilterFor(query)) : self.destinations;
      return results;
    };

    function createFilterFor(query) {
      var lowercaseQuery = angular.lowercase(query);

      return function filterFn(destination) {
        return angular.lowercase(destination.iata).indexOf(lowercaseQuery) === 0;
      }
    };

    function selectedItemChange(item) {
      if (typeof item !== 'undefined') {
        self.flightsTo = flightDataService.getFlightsTo(item.iata);
      }
    };

    function init() {
      flightDataService.loadAll().then(function() {
        self.destinations = flightDataService.getAirports();
        self.isFlightDataLoaded = true;
      }).catch(function(error) {
        console.log(error);
      });
    };

    self.init();
  });

Lastly, I attached my flightDataService.

It just returns saved data.

.factory('flightDataService', function($http, $q, papa) {
    var FLIGHT_DATA_URL = 'https://s3-ap-southeast-2.amazonaws.com/glow-dev-assets/flight-data-gz.csv';

    var self = this;
    self.flights = [];
    self.airports = [];
    self.routes = [];
    self.loadAll = loadAll;
    self.getAirports = getAirports;
    self.getFlightsTo = getFlightsTo;

    function loadAll() {
      var deferred = $q.defer();

      loadAirportData().then(function(airports) {
        loadFlightData().then(function(flights) {
          processFlightData(flights, airports);
          deferred.resolve();
        }).catch(function(error) {
          deferred.reject(error);
        });
      }).catch(function(error) {
        deferred.reject(error);
      });
      return deferred.promise;
    };

    function loadAirportData() {
      var deferred = $q.defer();
      $http({
        method: 'GET',
        url: '/assets/airports.json'
      }).then(function(response) {
        deferred.resolve(response.data);
      }, function(error) {
        deferred.reject(error);
      });
      return deferred.promise;
    };

    function loadFlightData() {
      var deferred = $q.defer();
      $http({
        method: 'GET',
        url: FLIGHT_DATA_URL
      }).then(function(response) {
        deferred.resolve(response.data);
      }, function(error) {
        deferred.reject(error);
      });
      return deferred.promise;
    };

    function processFlightData(data, airports) {
      self.flights = papa.parse(data, { header: true }).data;

      var dests = self.flights.map(function(flight) { return flight.destination });
      var destSet = new Set(dests);
      destSet.forEach(function(dest) {
        var airport = airports.find(function(airport) {
          return airport.iata === dest
        });

        if (typeof airport !== 'undefined') {
          self.airports.push(airport);
        }
      });

      self.flights.forEach(function(flight) {
        var origin = flight.origin;
        var dest = flight.destination;

        if (typeof self.routes[dest] === 'undefined') {
          self.routes[dest] = [];
        }
        if (typeof self.routes[dest][origin] === 'undefined') {
          self.routes[dest][origin] = {
            date: flight.date,
            delay: flight.delay,
            distance: flight.distance
          };
        }
      });

      return self.flights;
    };

    function getAirports() {
      return self.airports;
    };

    function getFlightsTo(destination) {
      return self.routes[destination];
    };

    return self;
  });

When I log ctrl.flightsTo on console.

It's always changed well.

But view stays same state as an empty array.

Gunwoo Kim
  • 63
  • 8
  • use `$scope` instead of `var self = this;` – Azad Dec 16 '16 at 09:21
  • @azad I already use `$scope` instead `self`. But it doesn't work. – Gunwoo Kim Dec 16 '16 at 09:30
  • did you call `$scope.apply()` directly after `self.flightsTo = flightDataService.getFlightsTo(item.iata);` this line? – Matthias Dec 16 '16 at 10:44
  • I see you are using an http call, this answer here might help you http://stackoverflow.com/a/15476728/5272567 might need to do a `$scope.apply()` directly after the http calls – Matthias Dec 16 '16 at 10:47
  • @Matthias I did call `$scope.$apply()` after `self.flightsTo = flightDataService.getFlightsTo(item.iata);` But it makes errors like `$digest already in progress` – Gunwoo Kim Dec 17 '16 at 04:33

4 Answers4

1

Finally, I found!

Because my flightsTo variable was associative array, Angular doesn't update it.

So I changed it to normal array.

Also, the opinion of IAmDranged was also right.

Thank you everybody!

Gunwoo Kim
  • 63
  • 8
0

I initially deleted this comment, since I haven't done angular in a while and got confused with the controlleras syntax, but it actually still might be usuefull in some ways.

ngRepeat uses $watchCollection to detect changes in the array - which will track changes to individual values in the array. This is what the Angular documentation says.

Trying to look a bit further, it actually sounds like the $watchCollection may work on a copy of the array - which would actually just be a copy of the reference to the array - as first initiliazed by the application.

In your case, your are assigning to your array - so you change the reference it points to - every time you update it. But $watchCollection is still only monitoring values changes in the original array to which it has kept a reference to.

You may want to set up an extra watch to look out for reference changes to your array.

This is rather a comment than an actual answer but this was too long to go in the comment section. Hope this is helpfull.

IAmDranged
  • 2,890
  • 1
  • 12
  • 6
0

This isn't so much an answer as a trick I sometimes use when I find myself in a similar situation: inject a $timeout and use it to make the final update outside the context of the $http call:

$timeout(function () {
    processFlightData(flights, airports);
    deferred.resolve();
}

Obviously this doesn't solve the problem or explain why it's happening, but it may be a practical workaround if you simply need to get your app working.

Rob Lyndon
  • 12,089
  • 5
  • 49
  • 74
0

Try this: When you use a simple javascript function angular does not apply the changes to variable inside this function.

function selectedItemChange(item) {
  if (typeof item !== 'undefined') {
    $scope.flightsTo = flightDataService.getFlightsTo(item.iata);
    $scope.$$phase || $scope.$apply();
  }
};

Or you can also use function expressions by put your function in $scope varianles like

 $scope.functionName = function () {}
Rob Lyndon
  • 12,089
  • 5
  • 49
  • 74
Manoj Lodhi
  • 978
  • 4
  • 10