Search code examples
javascriptangularjsasynchronous2-way-object-databindingdexie

Angular js two way data binding works only with apply


My angular app has/needs two way data binding.

I fetch data from indexedDB using angular service, which is injected in a controller.

I need the view to be updated after the fetch from db is made.

var app = angular.module("app",["ngRoute"]);

app.config(['$routeProvider',function($routeProvider) {
    //removed extra routes for simplicity
    $routeProvider.when('/grouped',{
        templateUrl: 'template/grouped.html',
        }).otherwise({
        redirectTo: '/grouped'
    });
}]);

The controller in the same file

 app.controller("AppController",function ($scope,dexiedb) {
    $scope.datais={};
    $scope.dataisdata={};
    $scope.alldata = {};

    var scholar = {};

    var _db;
    window.initial = 0;
    var callback_alldata = function(err,data){
      if(!err){
      /* If I put apply it works*/
      // $scope.$apply(function() {
        $scope.alldata = data;
            // });
      }
    console.log('in callback_alldata ',err,data);
    };

    var init = function() {
      console.log('hi in init');
      dexiedb.getdata(callback_alldata);
      console.log($scope);
    };
  init(); 
 });

The service that fetches data from db

app.service('dexiedb',function(){

  var createdData = {};
  var _db = null;

  var service = {};
  //Initialization code which makes connection
  service.init = function(){

  _db = new Dexie('puppets');
  _db.version(1).stores({
        snips: "++id, text",
        parent: "++id, title, timeStamp"
    });

   _db.open().then(function(){
      service.createdata();
      console.log('service.init then called');
     }).catch(function(e){
      console.log('error opening',e);
            });
  };
  //The actual place where data is fetched and returned
   service.createdata = function(callback){
      console.log('createdata');
        var alldata = [];
      _db.transaction('rw',_db.parent, _db.snips, function(){

        _db.parent.each(function(par){
          var r = {'parent':par,'snips':[]};
          _db.snips.where('URL').equals(par.URL).each(function(snip){
              r.snips.push(snip);
          });
          alldata.push(r);
          // console.log(alldata);
        }).then(function(){
          createdData = alldata;
          console.log('createdata',createdData);
          return callback(null,createdData);
          // return createdData;
        });
      });
    };
//The method which is called in the controller
service.getdata = function(callback){
  // console.log(createdData);
  if (Object.keys(createdData).length==0) {
    console.log('createdData was empty');
    return service.createdata(callback);
  } else return callback(null,createdData);
}

service.init();

return service;

 });

I understand that $scope.apply should be used if the place where variable is being updated is not in angular scope. While in callback won't the angular scope be present?

The data is fetched and logged in console but it does not show up in the view until I click on another route and then come back again.

My understanding of promises/callback isn't solid yet, is the problem due to mishandling of callbacks?


Solution

  • The issue is that Angular is not aware of the change made to $scope (i.e. $scope.alldata = data;).

    Changes made to the $scope in your controller will be updated immediately, however your code is inside a callback. Angular reaches the end of the controller before the callback is fired, and so it misses the digest cycle. It cannot know your callback was invoked.

    While you are updating the $scope in the callback, that doesn't trigger a new digest cycle.

    dexiedb does not appear to be using $http, you will therefore need to use $scope.$apply in this case. You can see angular's implementation of $http which includes $apply.