简体   繁体   中英

How to ensure factory promise resolves before directive executes

So far studying existing stackoverflow answers always helped me along, and I could always find an answer, but now I'm really stuck.

I'm building an app, which uses a directive to create calender month type boxes.

<app2directive class="column_50" jahr="2016" mon="November"></app2directive>

the directive code therefore isolates the scope and utilizes a templateUrl file to draw the calendar month

App.directive('app2directive',function( YEARS, MONTHS, DAYS){

return {
    restrict:"ACE",
    scope:{},
    replace:true,
    templateUrl:"templates/cal_directive3.html",
    controller: function ( $scope, $attrs, $injector, $log, YEARS, MONTHS, DAYS) {            var factoryName = "datumFactory" ;
        var factoryTonnen = "jsonsrc" ;
        var factoryInstance = $injector.get( factoryName ) ;
        var tonnenInstance = $injector.get( factoryTonnen ) ;

        var wtf = $scope.jsondata.get().then( function( promise){
            console.log( "jsondata", promise ) ;
            //$scope.tonnen = promise ;
        }, function( error){
            console.log( "error", error ) ;
        }) ;


});

At the moment i use an $injector to inject a factory which runs a $http-request to read a json-file with data such as holidays or other static information specific to the chosen year and month(s).

App.factory( 'jsonsrc', function( $http, $q ){ 
return {
    get: function(){
        var deferred = $q.defer() ;
        $http.get( 'termine_2016.json' )
            .success(function(data, status, headers, config) {
                deferred.resolve( data ) ;
            }).error(function(data, status, headers, config) {
                console.log( "[jsonsrc] request didnot work!" );
                deferred.reject( data ) ;
            }) ;
        return deferred.promise ;
    }
}

});

The effect of it is, that the same call to $http is run 12 times for a full year page load.

My concern is to refactor the file, so that I could preferably load the json data into the main-controller and the directive scope could inherit from the parent scope.

By its nature the call returns a promise. The directive would need means to wait for that promise to resolve before it should proceed, but right now I'm stuck on how to go about it. Thanks in advance for any pointers!

First $http is already returning a promise you can do :

return $http.get(...)

PS : you can chain promise so if you have some preprocessing to do you can do

return $http.get(..).then(function(response){
    var data = response.data;
    [..process..]
    **return data;**
}, function(rejection){
    // do some stuff
});

Second : Usually you bind data to your directive (ng-model for instance), and call services in controller (the view controller i mean). In order to handle the asynchronous loading of data you use the scope.$watch or attrs.$observe on the model to refresh your directive with the data loaded.

This enforces not to bind the directive with how your data loaded making them reusable, whatever the way you load your data or change it on your application.

Note what I put in bold, you musn't forget that or the next call to then won't have your processed data.

Finally : usually the link function provided by directive API you can just have :

link  : function(scope, element, attrs){
    attrs.$observe('myVariable', function(){
         // refresh your internal state of the directive here
    });
}

'myVariable' meanning in the call of your directive you have an attr my-variable :

and "myData" is loaded in the view controllerlike this :

jsonrc.get().then(function(response){
    $scope.myData = response.data;
});

If you want to go further I suggest you to build a service for your holidays service so you load the data only at startup of your application :

App.service('MyService',['$http', function($http){
    var promise = $http.get([URL]);
    return function(){  // this is your service
        var me = this;
        promise.then(function(response){
            this.data = response.data;
        });
    }
}]);

So now you can use in your main controller : scope.data = MyService.data; or even use it in your directive, or use some getter if you want, this is usually better but not always revelant.

If i forget anything tell me.

I think this could help.

  • First add the async call into a parent controller (directive's parent controller).
  • Then isolate your directive's scope. And add a model to it.

 scope: { data: '=', } 

  • On your controller add a variable like: $scope.directiveData = {}
  • Assign the value of that variable with the result of the async call.
  • Obviously pass it to your directive: <mydirective data="directiveData"></mydirective>
  • Use this variable on your template with the data name, or scope.data on link.

Probably you will need mocked data for directiveData, or just add an ng-if to prevent crashing (when trying to show data the first time, and the directiveData is empty object).

Have your factory save the httpPromise and create it only once.

App.factory( 'jsonsrc', function( $http ){
     var httpPromise;
     function load () {
         httpPromise = $http.get( 'termine_2016.json' )
             .then(function onFullfilled(response) {
                  //return data for chaining
                  return response.data;
             }).catch( function onRejected(response) {
                  console.log( "[jsonsrc] request didnot work!" );
                  //throw to reject promise
                  throw response.status;
             });
          return httpPromise;
      };
      function get () {
          if (httpPromise) return httpPromise;
          //Otherwise
          return load();
      };
      return { load: load,
               get:  get
             };
});

Notice that I removed the $q.defer and instead used the .then and .catch methods. The .success and .error methods have been deprecated; see the AngularJS $http Service API Reference -- deprecation notice .

You can then simplify your directive:

App.directive('app2directive',function(){
    return {
        restrict:"ACE",
        scope:{},
        replace:true,
        templateUrl:"templates/cal_directive3.html",
        controller: function ($scope, $attrs, jsonsrc, $log, YEARS, MONTHS, DAYS) { 
            jsonsrc.get().then (function (data) {
                $scope.tonnen = data ;
            }).catch ( function(error){
                console.log( "error", error ) ;
            });
        })
    }
});

Implemented this way, the jsonsrc factory executes the XHR GET only once, but each instantiation of the app2directive can retrieve the data for its own isolate scope.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM