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/.
Related
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)
angular
.module('password_forgot', ['app.auth'])
.controller('password_forgot', main);
main.$inject = ['auth'];
function main(auth) {
auth.sendEmail().then(function(){
//blablabla
});
}
Should I inject MyFactories 3 times? What is the best practice for doing this?
You're confusing three very different things:
a module: that's where various components (controllers, services, directives, filters) are registered. Modules can depend on other modules. In your example, you're defining a module named 'password_forgot' which depends on a module named 'MyFactories'. Your application is a module that depends on other modules, that depend on other modules, etc. The application is thus the union of all the components registered in all those modules. Note that factories are not angular components. A module named 'MyFactories' should probably rather be named 'MyServices'.
a service: that's an angular component that can be injected in other angular components. In your example, you're injecting a service named 'MyFactories' into the controller 'main'. It's very unusual, and probably an error, to name a service the same way as you name a module. You shouldn't do that. The name 'MyFactories' is a pretty bad name for a service. A service should have a specific responsibility, like 'translate', or 'authentication' or 'products'
a factory: a factory is a function that is registered into a module under a given name ('authentication', for example), and whose responsibility is to create and return the unique instance of the service of that name ('authentication'). This function is called once by angular, and the returned object or function is the service that is injected in the other components.
The line
main.$inject = ['MyFactories', '$scope'];
is only necessary if you minify your JS code. If you plan to do that, then I advise you to avoid inserting such a line of code by yourself, and to rely on ng-annotate, before the minification, to modify your code in order for it to be minifiable.
I was wondering when would you use $injector.get('someService') vs injecting that service directly.
Basically, what is the difference of the two code below?
angular
.module('myApp')
.controller('MyController', MyController);
/** Difference of this **/
MyController.$inject = ['$rootScope', '$route']; // and more services
MyController($rootScope, $route)
{
/* ... */
}
/** And this **/
MyController.$inject = ['$injector'];
MyController($injector)
{
var $rootScope = $injector.get('$rootScope');
var $route = $injector.get('$route');
/* and so on */
}
If you are not sure what your controller needs, or that you will update it in the future, and you have it refactored, then using $injector it is a lot easier to add/remove dependencies.
The first approach uses dependency injection while the second uses service locator pattern. Dependency injection has the following advantages:
It makes a component's dependencies explicit. For example, you can look at the constructor function of a controller and immediately know what dependencies it needs. With service locator, you usually have no idea because the controller might invoke the service locator any time.
It makes unit test easier. For example you can use the $controller service to instantiate a controller and feed it with mock objects via the locals argument. That's certainly easier than having to define a bunch of AngularJS services or factories so that $injector can resolve them. It becomes worse when coupled with #1: you might not be aware of all dependencies a component needs in order to supply all the necessary mocks.
Service locator does offer some flexibility thought. For example, your code might use a service only if it exists, like this:
if ($injector.has('serviceA')) $injector.get('serviceA').doSomething()
else someSomethingElse()
With dependency injection, if serviceA doesn't exist by the time your component is constructed, you'll get an error.
As to ease of refactoring, I don't think it's difficult to add/remove parameters to the constructor function. As pointed out in a comment to your question, tools like ngAnnotate will help make the declaration DRYer.
Therefore, I would just stick with dependency injection and only use service locator when absolutely necessary.
All of the controllers in my app share a dependency on a data provider type service I created. This service consists of functions to retrieve various bits of data, and almost all of these methods allow for an optional parameter. The ability to enter in this optional parameter is role based. The problem is my controllers are now full of code similar to:
// Initializing controller.
dataservice.getRole().then(function(role) { $scope.isAdmin = role.isAdmin; });
// After a button press or some other event.
if($scope.isAdmin) {
dataservice.getData($scope.param1, $scope.param2, $scope.optionalText);
} else {
dataservice.getData($scope.param1, $scope.param2);
}
It seems a code smell to me that I have to keep repeating this code throughout the controllers, but I can't think of a way to construct my controllers where I don't have to.
That's how I've realized that: Controller Inheritance.
Basically:
Create your service (factory) with your Base Controller logic and
return the Constructor (example)
Use $injector.invoke in your derived Class (to implement your
base controller) in order to enrich the actual controller $scope
(example)
There's no an official "angular way" to implement this features, but I hope the angular team will work on it
I'm using an angularJS and requireJS seed which you can download here: LINK
In that seed only the controllers that are called download the relevant controller which is then triggered. I've been trying to call a factory from my controller (with no luck) plus I would like the services/factories only to download the relevant factory if it has been called.
I've attempted to require a function within the factory method (much like the controller) but it is not working.
This is where I left off: Plunkr link
user971824.
I've put together something I call couchPotato that lazy-registers just about anything in angular using requirejs and the resolution features of $routeProvider (or any other router that does lazy promise-based resolution.
I've created a plunker based on yours demonstrating how you could do it with couchPotato. If you take a look at it, I think you'll see that it's a bit simpler because you don't actually create modules for all of the things you register lazily.
couchPotato grew out of some other example apps I found on the web. What I wanted was a tight way to do the lazy registration and a provider/service combo seemed ideal. I also wanted to maintain the ability for one component to depend on another, like in your example, you want the controller to depend on the factory... couchPotato lets you specify those dependencies in requirejs syntax.
So your controller, in my rendition, looks like this:
define(['app', 'myFactory'], function(app) {
app.couchPotato.registerController([
'mycontroller',
[
'myFactory',
function(myFactory) {
var message = myFactory.getCustomers();
alert(message);
}
]
]);
});
In this example, I made the controller, the factory and the value all lazy, but you could pick and choose and have some registered the "old fashioned way" at configuration time and others registered with couchPotato when they're needed for a given route.
http://plnkr.co/edit/Z3v1mszQiiq024po8Ocp?p=preview
A couple of things to note:
1) I put in a default route in order to trigger the lazy loading of your controller, your service (factory) and the version value.
2) I modified your service to append the version just to show how one component can depend on another (the service depends on the version value, the controller depends on the service).
So, within the require configuration, you don't actually specify any of this... it's all done lazily within your route.
$routeProvider.when('/',
$couchPotatoProvider.resolveDependenciesProperty({
templateUrl:'home.html',
controller: 'mycontroller',
dependencies: [
'mycontroller'
]
})
);
Since mycontroller depends on myFactory and myFactory depends on version, by the time your route is displayed they are all available and hooked up. I put some dummy text in the home.html partial just for kicks, but the controller is assigned by the $routeProvider so you don't actually need to specify it in the template.
couchPotato lives at https://github.com/afterglowtech/angular-couchPotato if you'd like to see a couple of other samples. I shim'ed it in dependent on angular because I designed it to be usable in cases where an entire application doesn't necessarily use requirejs... thus if you are loading angular with requirejs, you need to make couchPotato dependent on angular using the shim/deps technique.
LMK if you have any questions/comments... hope this helps!