Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 it via ng-route:

Controller code looks like this:

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

Routing:

...
.when('/someUrl', {
        templateUrl : 'some.html',
        controller : 'MyController',
        resolve : {
          data: ['Service', function (Service) {
            return Service.getData();
          }]
        }
})
...

when I go to /someUrl, everything works.

But I need to use this controller in other way(I need both ways in different places):

<div ng-controller="MyController">*some html here*</div>

And, of course, it fails, because 'data' dependency wasn't resolved. Is there any way to inject dependency into controller when I use 'ng-controller' or I should give up and load data inside controller?

like image 260
Rasalom Avatar asked Nov 14 '14 11:11

Rasalom


3 Answers

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.
              return Service.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 through
          dataService: {
            data: result
          }
        });
      });
    }
  ])
  .service('Service', ['$timeout',
    function($timeout) {
      return {
        getData: function() {
          //return a test promise
          return $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 resolve
    var _result = {
      data: null
    };
    Service.getData().then(function(result) {
      _result.data = result;
      return result;
    });
    return _result;
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-route.min.js"></script>
<div ng-app="myApp">
  <a href="#/">By ng-controller</a> |
  <a href="#/byResolve">By Resolve</a> |
  <a href="#/byWrapperController">By Wrapper Controller</a>
  <div ng-view />
</div>
like image 99
JcT Avatar answered Nov 12 '22 06:11

JcT


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

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

module.service('userService', function(Service){
    return Service.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.
    }
  ]
);
like image 3
KanhuP2012 Avatar answered Nov 12 '22 04:11

KanhuP2012


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

'<div ng-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

<script type="text/ng-template" id="modal.html">
    <div class="modal-body">
        <div ng-include="some.html"  ng-controller="MyController"></div>
    </div>
    <div class="modal-footer">
        <button class="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

like image 3
Orlov S Avatar answered Nov 12 '22 04:11

Orlov S