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.
Related
I have an app with AngularJS and TypeScript
I want to know is there any case when I should use AngularJS factory instead of TypeScript Static Class
Nothing changes with ES6/TypeScript classes. They are just syntactic sugar for JavaScript constructor functions.
Good use cases for factory services can be figured out by the process of elimination.
Since service services are preferable for OOP-flavoured units:
class FooClass {
static $inject = [...];
constructor(...) {}
}
app.service('foo', FooClass);
And value services are preferable for singleton units that aren't constructed from classes and don't involve DI:
app.value('bar', 'bar');
app.value('Baz', BazClass);
factory services can be used for the rest of the cases. I.e. when a service needs DI and returned value cannot be easily constructed from a class - a function, a primitive, an object that is returned from function call:
app.factory('qux', ($q) => $q.all([...]));
We had same problem two years ago. The decision was to stay with angular system and only build angular services. Neither angular factory or typescript static. The reason was we could track how service has been created and shared. (by hard)
To answer your question, angular factory is an object which still need injection base on angular system. It is good if you would like keep tight with angular.
On the other hand typescript is more general. When you call a static function, it is just a function. Like you can import it anywhere else not angular, and then use it.
Something about javascript pattern:
A common pattern for creating objects in JavaScript is the module pattern.The pattern involves returning an object from the function that will be your public API.
There is a popular variation to the module pattern called the revealing module pattern. What happens with the revealing module pattern is things start explicitly private, and then choices are made about whether they become public.
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)
Testing modules dependent on other modules, and providers and services in them - possible. Let's say we need to test module 'A' that depends on module 'B':
totally not coffeescript, rather pseudo code for brevity :)
beforeEach ->
angular.module('B',[])
module ($provide)->
$provide.provider 'foo', ->
this.$get = -> # ...etc...etc.
it 'testing module A', ->
module('A')
inject -> # ...etc...etc.
Now, imagine we have quite a few modules that need to be mocked, and many services and providers. So how would you move faking 'B' part into a separate file?
and how they'd be used after? It's quite possible to use module loaders (webpack, browserify) but how would one do without? Using angular capabilities and what Karma offers out of the box?
Is it possible to wrap entire mock into an angular module and load it in beforeEach?
Yes, actually, making services their own modules can easily be tested in Angular using Karma.
As long as you have your testing framework set up properly—Angular mocks, and all of the required modules are loaded into Karma (check the Karma config)—then this is fairly straight-forward.
The below example assumes FirstService is the service you are testing, and SecondService is another service that FirstService depends on and interacts with.
describe('FirstService', function () {
var SecondService;
beforeEach(module('FirstService'));
beforeEach(inject(function (_SecondService_) {
SecondService = _SecondService_;
spyOn(SecondService, 'doSomethingElse');
}));
it('calls SecondService.doSomethingElse', function () {
FirstService.doSomething();
expect(SecondService.doSomethingElse).toHaveBeenCalled();
});
});
You include the module in a beforeEach as specified on the first line—do not use square brackets [] when including it as that redefines the module and you want to include the existing module as-is.
Although you should omit the square brackets when importing modules, you should be sure that when you are initially creating your FirstService module (e.g., in your app.js file, if that's how your project is set up) that you are indeed including SecondService as a dependency:
angular.module('FirstService', ['SecondService'])
The second beforeEach is injecting the SecondService module. It is already globally available to be injected and can be accessed by its module name with a leading and trailing underscore, i.e. _SecondService_. I've assigned it to a variable so that it is available to be used in the it statements, otherwise I would need to inject it in each it statement instead of a beforeEach.
I am creating a spy using Jasmine's spyOn to spy on the method doSomethingElse to ensure it has been called. The second parameter of spyOn (the method name) should have quotes but the first should not—it should be the variable you just defined for your SecondService.
The example unit test tests that calling FirstService.doSomething() calls SecondService.doSomethingElse().
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/.
I have two Angular modules, A and B.
A has no deps, some configurations and some filters:
angular.module('A', [])
.config(function ($httpProvider) {
// set common headers for $http requests
$httpProvider.defaults.headers.common = {...};
})
.filter('myFilter', function () {
// create a filter
});
B depends on A, so it goes something like:
angular.module('B', ['A'])...
I know that B depending on A means I can use myFilter inside B views.
The question is: does the code in myFilter benefits from the $http common headers I set inside the config() function of the A module (since myFilter belongs to A)?
And when making HTTP requests from inside the B module, are the common headers the ones I set inside A or they are not affected from the configuration inside A?
True to both the questions makes enough sense to me: I config some common headers in the A module that I'll use inside the filters that the A module provides, but I want those same common headers out of my way when I'm inside the B module; still, when I'm inside B views, I want to be able to use A filters with their specific common headers.
Here is what i think. Like any other angular $httpProvider is also singleton in nature which very well means any changes to it from any where including any module would affect other, or in other words changes are global.
In any one app, no matter what modules you pull in, all services, factories and providers are singletons.
You're using the $httpProvider, one such singleton... and therefore, by the very nature of singletons, any configuration done to a provider means all modules are affected.
Yes, this is how it works. See example.
angular.module("foo",[]).config(function($httpProvider){$httpProvider.test = 123});
angular.module("bar",["foo"])
.config(function($httpProvider){console.log(httpProvider.test)}); //123