Set app.config within a controller - angularjs

So, I have my app.controller and inside of it I have a request. When request is successful, I want to set retrieved data to some $provider property, i.e.
app.controller('someCtrl', function(){
app.config(function($someProvider){
$someProvider.property = response.data;
$someProvider.function(response.data.status);
})
})
I'm trying to set this within controller, but it does nothing. Any tips, guys? :)

if default injecting doesn't do the job
maybe store your provider at config time to some other variable and use this var in controller later
but angular says:
During application bootstrap, before Angular goes off creating all services, it configures and instantiates all providers. We call this
the configuration phase of the application life-cycle. During this
phase, services aren't accessible because they haven't been created
yet.
Once the configuration phase is over, interaction with providers is
disallowed and the process of creating services starts. We call this
part of the application life-cycle the run phase.
so i don't know if this will work

Newly added config blocks aren't executed after the ending of config phase, the same applies to other app methods because they result in additional config blocks internally.
It is possible with:
app.config(($provide, $someProvider) => {
$provide.value('$someProvider', $someProvider);
});
app.controller('someCtrl', ($someProvider) => {
$someProvider...
});
This voids the warranty and may indicate XY problem that should be solved the other way, depending on what $someProvider is and how it functions.
If $some is injected and used before setting $someProvider, this will result in race condition. If $some was changed at some point to not watch for $someProvider properties after instantiation, this will result in breaking changes without notice that will make the tests fail.
It is acceptable for badly designed third-party services that don't cause ill effects when being treated like that. And as any other hack, it should be thoroughly covered with tests.

Your question comes down to how can I postpone angular's bootstrap process until I have some async data.
For the async part, you can use (or do) whatever you want, it doesn't really matter. Fun fact: You can even use $http before bootstrapping angular just by getting it via angular.injector(["ng"]).get("$http");.
This being said, when you have all your async data, all that's left to do is to bootstrap angular. This can be achieved via angular.bootstrap - source.
Here's a working example in which I asynchronously attach a controller (remember, you can do whatever you want: attach various constants, config blocks, etc). I've used setTimeout for simplicity's sake.
// Initial Angular Code
angular.module('myApp', []);
// Async function. I've used setTimeout for simplicity's sake
setTimeout(function() {
angular
.module('myApp')
.controller('MyCtrl', ['$scope',
function($scope) {
$scope.value = 'Angular has started!';
}
]);
// Boostrap AngularJS
angular.bootstrap(document.getElementById('boostrap-me'), ['myApp']);
}, 1000);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<div id="boostrap-me" ng-controller="MyCtrl">
<div ng-bind="value">Angular hasn't yet bootstrapped as we're waiting for some async data!</div>
</div>

Related

AngularJS - Injection in config

In my app I need to inject "dateFilter" in the config block. I know I can't do it like this:
.config(function(dateFilter){})
Since dateFilter is not a provider or a constant, it's not available during config.
However, after some research, I made it work by using the following in the config:
angular.injector(["ng"]).get('dateFilter')('2014-01-01','yyyy/MM/dd');
Doesn't this mean that I can get anything during config? Then what's the point making only providers and constants injectable during config? Is it bad to do something like angular.injector(["ng"]).get('dateFilter') during config?
angular.injector shouldn't be used in production, unless the circumstances are really exotic (i.e. almost never). It creates a new injector instance and introduces some overhead. Conventional Angular DI is good for its testability, while angular.injector turns a part of the application into untestable piece of code. Always reuse current injector inside the app, if possible (i.e. almost always).
Usually 'how to use service instance in config block' type of questions indicates an XY problem. The fact that Angular uses config to configure service providers that thereafter will create service instances (chicken-egg dilemma) suggests that the application should be refactored to respect Angular life cycle.
However, built-in filters are stateless helper functions, and their use in config phase is relatively harmless. dateFilter service is defined by $filterProvider, and $filterProvider should be injected to get to dateFilterProvider. The problem is that dateFilter depends on $locale service, which wasn't instantiated yet. $locale is constant (in broad sense) that doesn't depend on other services, so it has to be instantiated too.
angular.module('...', [
'ngLocale' // if used, should be loaded in this module
])
.config(($filterProvider, $localeProvider, $provide, $injector) => {
var $locale = $injector.invoke($localeProvider.$get);
var dateFilterProvider = $injector.get('dateFilterProvider')
var dateFilter = $injector.invoke(dateFilterProvider.$get, { $locale: $locale });
$provide.constant('dateHelper', dateFilter);
})
This is a hack should be taken into account in tests (dateHelper service should superficially tested) but is relatively trouble-free and idiomatic.
you cant inject services in config only provides but you can do it in app.run here's the calling order:
app.config() //only provides can be injected
app.run() //services can be injected
directive's compile functions (if they are found in the dom)
app.controller()
directive's link functions (again, if found)

When is an AngularJS service re-initialized?

What is the lifecycle of a service (or factory) in angularjs and when is it re-initialzed ?
When Angular bootstraps, it attaches the constructor functions for services to the associated module. This happens once.
angular
.module('myApp')
.service('User', function UserConstructor() {
// stuff
});
When a you try to run a controller or something that depends on a certain service, Angular will inject it for you.
app.controller('FirstCtrl', function FirstCtrlConstructor(User) {
// User is available here
});
Under the hood, angular uses this thing called $injector to do dependency injection for you. It works something like this:
var $injector = {};
$injector.cached = [];
$injector.get = function(service) {
// check if service is in this.cached
// if so, return it
// if not
// 1) instantiate the service
// 2) store it in this.cached
// 3) return it
};
So when Angular sees that it needs to inject User into FirstCtrlConstructor, it calls $injector.get('User') to get the User. Since it hasn't injected User anywhere else before, it'll hit the "if not" condition and:
Call new User().
Store it in $injector.cached for next time.
Return it.
Now let's say that we need to inject User a second time:
app.controller('SecondCtrl', function SecondCtrlConstructor(User) {
// stuff
});
Again, when Angular sees that SecondCtrlConstructor depends on User, it calls $injector.get('User') to get the User so it could inject it. This time, it hits the "if so" condition. Since we previously put User in $injector.cached, it found it there for us. Thus, User doesn't get instantiated again.
Say we have a ThirdCtrl that also depends on User. It too would find it in $injector.cached, and so it wouldn't instantiate UserConstructor. Say that we have myDirective that depends on User. The same thing would happen - it'd find it in $injector.cached and thus wouldn't instantiate it.
This is called the Singleton pattern. It's used when you don't want to instantiate something more than once. Angular uses this for services so they don't get instantiated more than once. The same is true for factories (and probably also true for providers; probably not true for values and constants).
See https://medium.com/#adamzerner/dependency-injection-in-angular-18490a9a934 for some more info.
Services/factories are only initialized once, the first time they are used. From the docs: https://docs.angularjs.org/guide/services
Angular services are:
Lazily instantiated – Angular only instantiates a service when an
application component depends on it.
Singletons – Each component dependent on a service gets a reference to the single instance
generated by the service factory.

Why shouldn't I access the factory function through a provider.$get in the config phase?

First of all, this is an honest question. And I'm looking for honest and justified answers on why I shouldn't be doing this...
angular
.module('X', ['Y'])
.config(function (myFactoryProvider, myServiceProvider) {
myFactoryProvider.$get().myFn();
myServiceProvider.$get().myFn();
});
angular
.module('Y', [])
.factory('myFactory', ['$location', function ($location) {
return {
myFn: function () {
console.log('factory');
console.log($location.absUrl());
}
}
}])
.service('myService', ['$location', function ($location) {
this.myFn = function () {
console.log('service');
console.log($location.absUrl());
}
}]);
Here's a JSFiddle: http://jsfiddle.net/1vetnu6o/
This is working as you can see above and it solves a few problems for me. But I shouldn't probably be doing this and I want to understand why. I really need good reasons to not do this. Despite the fact that I really want to.
tl;dr;
Here's the context of the problem...
I have this internal framework used by multiple products where there's this one service (which happens to be a factory) that basically contains a group of related helper methods. In this case device related like isMobileDevice, isAndroid, getDeviceType (with returns mobile, tablet or desktop), and few others...
This service must be injected into the config() phase of the application using the framework because we need access to the getDeviceType function. The thing is, we need to get the deviceType to load proper templates with $routeProvider. It's in the config() phase that we are building the correct template paths to be used for all the routes. Some of them depend on the deviceType, while others have a generic template independent of the device.
Since this is a service, we cannot inject it directly into the config() phase but we can call that method using the technique I mentioned earlier in the post.
How I'm currently solving this? The helper service is actually a provider and all the methods are exposed both in the provider section as well as in the factory function. Not ideal, but it works. I consider this a work-around, but I'd rather have a work-around in the application and not the framework, thus the technique first mentioned.
Thoughts?
I didn't knew but actually you can invoke on config phase.
The problem is that myService will be instantiated twice :/
angular
.module('X', [])
.config(function(myServiceProvider) {
myServiceProvider.$get().myFn();
})
.run(function(myService) {
myService.myFn();
})
.service('myService', ['$location', function($location) {
console.log('myService!');
this.myFn = function() {
console.log($location.absUrl());
}
}]);
output:
"myService!"
"location"
"myService!"
"location"
This is dangerous if you call $get on config of a service that has a big nested dependency tree :/
I think the correct approach, if you need a utility service (with no dependency), is to use a constant (in this way you can inject it everywhere). Otherwise if you need dependencies use a service and stick to the run() block.
The config() block it's the place to instruct your services how they should work with the help of their providers.
The run() block it's the perfect place to do some logic when your app starts (aka main method).

Angular: Optionally configure a service

I have a service that has an internal list of directive names (lets call it listService). In the spirit of loosely coupled applications I want other modules to be able to add their own directives to that list instead of statically defining it in the service module.
I understand that I can create a provider in order to configure a service like this:
app.provider("listService", ServiceProvider);
app.config(["listServiceProvider", function(listServiceProvider) {
listServiceProvider.add("myDirective");
}]);
At the same time, I don't want all the modules that define directives to depend on the service. So what I would like to do is "if this service is used in this project: configure it; else: ignore".
Unfortunately, the above code produces a module error if listServiceProvider is not available. I also tried to use a decorator with the same result.
So how can I optionally configure a service in order to have a loosely coupled application?
Like I mentioned in the comment, it is important to have a broader context for why you need to register directives (or their names).
In absence of a broader understanding, if, generally speaking, you need to conditionally check for existence of a service (or service provider, in config), you can use the $injector:
.config(function($injector){
var listServiceProvider;
if ($injector.has("listServiceProvider")) {
listServiceProvider = $injector.get("listServiceProvider");
}
if (listServiceProvider){
listServiceProvider.add("myDirective");
}
});
If I understand correctly; you want a service that tracks which of your directives have been added into the Angular app, and for the directives and the service to be decoupled from each other so they can be included on demand.
I don't think the module pattern will do this for you, since the services and directives are injected at load time. Optional dependency injection is not possible.
However, you could fire an event from your directive and pick it up in your service, removing the need for dependency injection altogether.
myDirective
.run(['$rootScope', 'LIST_SERVICE_EVENT',
function($rootScope, LIST_SERVICE_EVENT) {
$rootScope.$emit(LIST_SERVICE_EVENT, 'myDirective');
}]);
listService
.run(['listService', '$rootScope', 'LIST_SERVICE_EVENT',
function(listService, $rootScope, LIST_SERVICE_EVENT) {
$rootScope.$on(LIST_SERVICE_EVENT, function(ev, name) {
listService.add(name);
});
}]);
Fiddle: http://jsfiddle.net/bdpxhLg3/4/.

How to "eager load" a service in AngularJS? (instantiate it before its needed, automatically)

I'm trying to achieve a program structure like this:
The problem here is, when there is no apparent controller using the Features in the beginning, they are not instantiated and not registered in the FeatureRegistry, therefore they can't show up in the View. But what I would like is to achieve is that they show up in the view, then there template is loaded via ng-include and then in the template there are specific controllers for each feauture. These controllers are the ones that are using the Features.
The features are basically only there to tell about the location of templates and icons, which to use, and also to kick off the start of the Feature.
But back to my initial question:
How to instantiate the services even if they are not needed at the moment?
Or is there another function, that I can use for that instead of service? I would also like if you point me to that then :)
You can ask for it in the run part of your application, injector will invoke it.
angular.module("myApp", []).
factory("EagerService", function () {
console.log("I'm ready.");
}).
run(function (EagerService) {
console.log("EagerService is ready.");
});
Yet, as far as I understand, you have child/sub controllers that need this EagerService. Why don't you inject it there?
(Since this is relatively old - this answer is for future readers - but I stumbled across this question so maybe someone else will too) If you use providers/config blocks - they are done eagerly, so it's better to do eager initialization code there. You are/were probably thinking in terms of services/run blocks.
To demonstrate with code, this alert will not pop (assuming myServiceModule is a module that your application depends on and myService is not injected anywhere):
angular.module('myServiceModule', []).service('myService', function () {
alert("service");
// service
return {};
});
However this alert will pop even if no one is depending on the myProvider service:
angular.module('myProviderModule', []).provider('myProvider', function () {
alert("provider");
// Define your service here. Can be an array of inject-ables
// instead of a function.
this.$get = function () {
// service
return {};
};
});
You can see this in action in this plunker.
Read more about providers in the official documentation here.

Resources