I need to inject an extra object to my controllers dynamically so I thought it'd be best to do it in the run function like so:
angular.module("app").run([
"$rootScope", "$inject", "repository.user", function ($rootScope, userRepository) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
var controller = next.$$route.controller;
userRepository.getSession(function(data) {
// What do do now?
});
});
}
]);
I'd like to inject that returned data into my controllers but I'm not sure how to do it?
Take a look at the $routeProvider documentation, specifically the resolve configuration option for a route. Have you thought about using this to dynamically resolve your dependency?
resolve - {Object.=} - An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired. The map object is:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}: If string then it is an alias for a service. Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before its value is injected into the controller. Be aware that ngRoute.$routeParams will still refer to the previous route within these resolve functions. Use $route.current.params to access the new route parameters, instead.
If I understand correctly, you are trying to collect user login state for every route change. Why not just save the data in $rootScope as something like $rootScope.currentUser = data.
This way you can access $rootScope.currentUser from any controller.
Related
Method on $viewcontentloaded is firing asynchronously. To detail my problem, I have a variable in root scope i.e. my Main controller, which need to be initialized before my view controller loads. In module.run I am calling a sync function to initialize $rootScope.session. And In my view controller of a route, I am checking the status of session in afunction that is called like
$scope.$on('$viewContentLoaded', function() {
$scope.initialize();
});
But some times on page refreash, I am getting an undefined value for $rootScope.session, as It may have initialized later. So, Is there any way to make this synchronous like rootscope will be initialized before view loads. And for curiosity, how it will affect, if I call the $scope.initialize(); normally in my controller, in $viewContentLoaded or in $routeChangeSuccess.
Thanks in advance.
So, Is there any way to make this synchronous like rootscope will be initialized before view loads.
Use the $controller service to manually create the controller, as in a unit test.
$controllerProvider.register('FooCtrl', FooCtrl);
ctrl = $controller('FooCtrl', {$scope: scope});
Or $broadcast a custom event from the main controller down to the child:
function mainCtrl($rootScope)
{
$rootScope.$broadcast('abc');
}
function secondCtrl($scope)
{
$scope.$on('abc', function(event) { $scope.initialize(); });
}
Or use a try/catch block and a recursive call with a timer.
These are more or less the steps that you would take to implement lazy loading in AngularJS. In summary, you would first define your app module to keep instances of the relevant providers. Then you would define your lazy artifacts to register themselves using the providers rather than the module API. Then using a ‘resolve’ function that returns a promise in your route definition, you would load all lazy artifacts and resolve the promise once they have been loaded. This ensures that all lazy artifacts will be available before the relevant route is rendered. Also, don’t forget to resolve the promise inside $rootScope.$apply, if the resolution will be happening outside of AngularJS. Then you would create a ‘bootstrap’ script that first loads the app module before bootstrapping the app. Finally, you would link to the bootstrap script from your ‘index.html’ file.
References
AngularJS source: controllerSpec.js
Ifeanyi Isitor: Lazy Loading In AngularJS
AngularJS Lazy Loading with Require.js
Split Large AngularJS Controllers using the Mixin Pattern
I have a route definition as follows:
.state('user_login', {
url: '/user/login',
templateUrl: 'login.tpl.html',
controller: 'AuthenticationCtrl',
resolve: {
practice: ['$q', function($q) {
return $q.when({});
}]
}
})
Things work as expected when I inject "practice" into the controller. When I use the $injector, service however:
$injector.get('practice')
I get an unknown provider exception. Are resolve objects not available to the $injector? How I can expose them in the controller without explicitly injecting them in the controller definition?
Note: I am using Angular 1.2.x
No, you cannot get them separately via $injector. And you cannot even inject them separately as well in other places, say the same controller (AuthenticationCtrl) instantiated by ng-controller directive.
Resolve objects are not any service or any other entity which can be injected separately. It is a special dependency injected by the router when the controller AuthenticationCtrl is bound via the router. You cannot get the instances separately. Only router knows about the resolve properties and while the router instantiates the controller (once all the resolve dependencies are resolved) it looks for resolve properties in the annotation (of the dependency list specified via explicit/implicit dependency annotation in the definision of AuthenticationCtrl) of the route-bound controller and injects them as required.
This kind of special implementation can be found in other components as well like, angular-ui-modal, ui-state-router, angular-router's routeprovider etc..
My directive calls some async service based on $http. Service name comes to derectiva as a parameter:
.directive('lookup', ['$scope', function($scope, svc) {
var injector = angular.injector(['app', 'ng']);
var svc = injector.get(scope.serviceName);
...
$scope.clickHandler = function () {
svc.lookup($scope.inputText).then(function (res) {
$scope.inputText = res.data.name;
});
}
...
})
While setting chrome breakpoint inside then() handler we can see by call stack that it's fired within $apply(), so every scope change should be reflected in DOM. $q documentation also states that handlers should be synchronized with scope/dom update cycle, but my view is not updated after clickHandler(). Calling explicit scope.$digest() after scope change does the thing, but don't know why it does not work withouth it. Another strange thing I've noticed is that on the breakpoint $scope has no parent-child relation with $rootScope for which http response handler is wrapped by $apply(). Maybe this is the reason of my troubles. Am I doing something wrong?
Real code is hard to cite, but see essential example code on plunker
Explanation:
The villain of the piece is the use of angular.injector in the link function:
var injector = angular.injector(['ng', 'app']);
var lookupService = injector.get(scope.serviceName);
This code will create a new injector function instead of retrieving the one already created for your application when it was bootstrapped.
Services in Angular are singletons in the sense that they are only created once per injector. In your case this means that injector.get(scope.serviceName) will return a new instance of the service and not the same instance as you possibly would've interacted with before.
The same goes for the $http service that your lookupService uses. In turn, $http injects $rootScope to trigger the digest cycle by calling $apply on it after a XHR request. But as the $rootScope will not be the same as for the rest of your application, the digest cycle will be in vain.
This is also the reason for the behaviors you are witnessing, and why an explicit call to scope.$digest() helps, as it will trigger the digest cycle on the correct scope chain.
Solution:
Inject the $injector service in your directive and use that to retrieve the service you want:
.directive('lookup', ['$injector',
function($injector) {
function link(scope, elem) {
var lookupService = $injector.get(scope.serviceName);
Demo: http://plnkr.co/edit/nubMohsbHAEkbYSg39xV?p=preview
I have an issue where i want fire a method from a service immediately after a route change. That method has to lookup an object on a Firebase:
app.service('document',function($q,$firebase){
var databaseReference = new Firebase(firebase),
database = $firebase(databaseReference);
return{
getDocument: function(title){
return database.documents[title];
}
}
});
unfortunately, the documents property won't load directly; it takes a few ms to appear.
I know that i could wrap this up in a promise, but how can i make the service being returned only when that promise is resolved?
If you use Angular ui-router, you can put one or more promises in a resolve block of the same state your controller is in, and declare those as dependencies to be injected into your controller. The controller then does not get created until all those promises have been resolved, and within your controller you'll have access to the resolved promises. See https://github.com/angular-ui/ui-router/wiki#resolve for more info.
I'm trying to figure out AngularJS and routing. Can someone take a look at this http://jsfiddle.net/spoon16/p9BBr/
Help me understand why $routeParams.i is undefined during initialization. What is the appropriate way to use $routeParams in my controllers?
Basically $routeParams isn't available until the route service has changed the route, meaning you should only inject the service in controllers associated with a specific route (the controller property for the route).
If you in some other controller needs to know current route etc. you should listen to the various events the route service broadcasts, e.g.
$scope.$on('$routeChangeSuccess', function (ev, current, previous) {
// ...
});
Updated fiddle.