Skip to content Skip to sidebar Skip to footer

Angularjs Ng-controller With Resolve

I've ran into problem with ng-controller and 'resolve' functionality: I have a controller that requires some dependency to be resolved before running, it works fine when I define i

Solution 1:

In the below, for the route resolve, we're resolving the promise and wrapping the return data in an object with a property. We then duplicate this structure in the wrapper service ('dataService') that we use for the ng-controller form.

The wrapper service also resolves the promise but does so internally, and updates a property on the object we've already returned to be consumed by the controller.

In the controller, you could probably put a watcher on this property if you wanted to delay some additional behaviours until after everything was resolved and the data was available.

Alternatively, I've demonstrated using a controller that 'wraps' another controller; once the promise from Service is resolved, it then passes its own $scope on to the wrapped controller as well as the now-resolved data from Service.

Note that I've used $timeout to provide a 1000ms delay on the promise return, to try and make it a little more clear what's happening and when.

angular.module('myApp', ['ngRoute'])
  .config(function($routeProvider) {
    $routeProvider
      .when('/', {
        template: '<h1>{{title}}</h1><p>{{blurb}}</p><div ng-controller="ResolveController">Using ng-controller: <strong>{{data.data}}</strong></div>',
        controller: 'HomeController'
      })
      .when('/byResolve', {
        template: '<h1>{{title}}</h1><p>{{blurb}}</p><p>Resolved: <strong>{{data.data}}</strong></p>',
        controller: "ResolveController",
        resolve: {
          dataService: ['Service',
            function(Service) {
              // Here getData() returns a promise, so we can use .then.// I'm wrapping the result in an object with property 'data', so we're returning an object// which can be referenced, rather than a string which would only be by value.// This mirrors what we return from dataService (which wraps Service), making it interchangeable.returnService.getData().then(function(result) {
                return {
                  data: result
                };
              });
            }
          ]
        }
      })
      .when('/byWrapperController', {
        template: '<h1>Wrapped: {{title}}</h1><p>{{blurb}}</p><div ng-controller="WrapperController">Resolving and passing to a wrapper controller: <strong>{{data.data ? data.data : "Loading..."}}</strong></div>',
        controller: 'WrapperController'
      });
  })
  .controller('HomeController', function($scope) {
    $scope.title = "ng-controller";
    $scope.blurb = "Click 'By Resolve' above to trigger the next route and resolve.";
  })
  .controller('ResolveController', ['$scope', 'dataService',
    function($scope, dataService) {
      $scope.title = "Router and resolve";
      $scope.blurb = "Click 'By ng-controller' above to trigger the original route and test ng-controller and the wrapper service, 'dataService'.";
      $scope.data = dataService;
    }
  ])
  .controller('WrapperController', ['$scope', '$controller', 'Service',
    function($scope, $controller, Service) {
      $scope.title = "Resolving..."; //this controller could of course not show anything until after the resolve, but demo purposes...Service.getData().then(function(result) {
        $controller('ResolveController', {
          $scope: $scope, //passing the same scope on throughdataService: {
            data: result
          }
        });
      });
    }
  ])
  .service('Service', ['$timeout',
    function($timeout) {
      return {
        getData: function() {
          //return a test promisereturn $timeout(function() {
            return"Data from Service!";
          }, 1000);
        }
      };
    }
  ])
  // our wrapper service, that will resolve the promise internally and update a property on an object we can return (by reference)
  .service('dataService', function(Service) {
    // creating a return object with a data property, matching the structure we return from the router resolvevar _result = {
      data: null
    };
    Service.getData().then(function(result) {
      _result.data = result;
      return result;
    });
    return _result;
  });
<scriptsrc="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script><scriptsrc="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-route.min.js"></script><divng-app="myApp"><ahref="#/">By ng-controller</a> |
  <ahref="#/byResolve">By Resolve</a> |
  <ahref="#/byWrapperController">By Wrapper Controller</a><divng-view /></div>

Solution 2:

Create a new module inside which you have the service to inject like seen below.

varmodule = angular.module('myservice', []);

module.service('userService', function(Service){
    returnService.getData();
});

Inject newly created service module inside your app module

angular.module('myApp')
  .controller('MyController', ['$scope', 'myservice', function ($scope, myservice) {
      $scope.data = data;
    // now you can use new dependent service anywhere here.
    }
  ]
);

Solution 3:

You can use the mechanism of the prototype.

.when('/someUrl', {
    template : '<div ng-controller="MyController" ng-template="some.html"></div>',
    controller: function (data) { 
        var pr = this;
        pr.data = data;
    },
    controllerAs: 'pr',
    resolve : {
        data: ['Service', function (Service) {
            return Service.getData();
        }]
    }
})

angular.module('myApp')
  .controller('MyController', ['$scope', function ($scope) {
      $scope.data = $scope.pr.data; //magic
    }
  ]
);

Now wherever you want to use

'<divng-controller="MyController"></div>'

you need to ensure that there pr.data in the Scope of the calling controller. As an example uib-modal

var modalInstance = $modal.open({
    animation: true,
    templateUrl: 'modal.html',
    resolve: {
        data: ['Service', function (Service) {
            return Service.getData();
        }]
    },
    controller: function ($scope, $modalInstance, data) { 
        var pr = this;
        pr.data = data;
        pr.ok = function () {
            $modalInstance.close();
        };
    },
    controllerAs:'pr',
    size:'sm'
});

modal.html

<scripttype="text/ng-template"id="modal.html"><divclass="modal-body"><divng-include="some.html"ng-controller="MyController"></div></div><divclass="modal-footer"><buttonclass="btn btn-primary pull-right"type="button"ng-click="pr.ok()">{{ 'ok' | capitalize:'first'}}</button></div></script>

And now you can use $scope.data = $scope.pr.data; in MyController

pr.data is my style. You can rewrite the code without PR. the basic principle of working with ng-controller described in this video https://egghead.io/lessons/angularjs-the-dot

Solution 4:

Presuming that Service.getData() returns a promise, MyController can inject that Service as well. The issue is that you want to delay running the controller until the promise resolves. While the router does this for you, using the controller directly means that you have to build that logic.

angular.module('myApp')
  .controller('MyController', ['$scope', 'Service', function ($scope, Service) {
    $scope.data = {}; // default values for data 
    Service.getData().then(function(data){
      // data is now resolved... do stuff with it$scope.data = data;
    });
  }]
);

Now this works great when using the controller directly, but in your routing example, where you want to delay rendering a page until data is resolved, you are going to end up making two calls to Service.getData(). There are a few ways to work around this issue, like having Service.getData() return the same promise for all caller, or something like this might work to avoid the second call entirely:

angular.module('myApp')
  .controller('MyController', ['$scope', '$q', 'Service', function ($scope, $q, Service) {
    var dataPromise,
      // data might be provided from router as an optional, forth param
      maybeData = arguments[3]; // have not tried this before$scope.data = {}; //default values// if maybeData is available, convert it to a promise, if not, //    get a promise for fetching the data
    dataPromise = !!maybeData?$q.when(maybeData):Service.getData();
    dataPromise.then(function(data){
      // data is now resolved... do stuff with it$scope.data = data;
    });    
  }]
);

Solution 5:

I was trying to solve the problem using ng-init but came across the following warnings on angularjs.org

The only appropriate use of ngInit is for aliasing special properties of ngRepeat, as seen in the demo below. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.

So I started searching for something like ng-resolve and came across the following thread:

https://github.com/angular/angular.js/issues/2092

The above link consists of a demo fiddle that have ng-resolve like functionality. I think ng-resolve can become a feature in the future versions of angular 1.x. For now we can work around with the directive mentioned in the above link.

Post a Comment for "Angularjs Ng-controller With Resolve"