This tutorial doesn't talk much about how modules fit in the injector tree except for the this phrase:
All requests bubble up to the root NgModule injector that we
configured with the bootstrapModule method.
Does it mean that if a provider is not found on any component up the tree, only the root module is checked and no other intermediary module is checked? For example, the picture below shows the component tree with one root component and with components 3 and 4 being declared in it's own module. The root module has it's own provider and the red module has it's own provider. When component 3 requests the service, is the provider on red module ignored and the provider on parent component checked and if not found than provider on root module is returned?
I think you are looking for https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#!#q-module-provider-visibility
Why is a service provided in a feature module visible everywhere?
Providers listed in the #NgModule.providers of a bootstrapped module
have application scope. Adding a service provider to
#NgModule.providers effectively publishes the service to the entire
application.
When we import a module, Angular adds the module's service providers
(the contents of its providers list) to the application root injector.
This makes the provider visible to every class in the application that
knows the provider's lookup token.
This is by design. Extensibility through module imports is a primary
goal of the Angular module system. Merging module providers into the
application injector makes it easy for a module library to enrich the
entire application with new services. By adding the HttpModule once,
every application component can make http requests.
However, this can feel like an unwelcome surprise if you are expecting
the module's services to be visible only to the components declared by
that feature module. If the HeroModule provides the HeroService and
the root AppModule imports HeroModule, any class that knows the
HeroService type can inject that service, not just the classes
declared in the HeroModule.
The providers of the modules are added to the root scope. Duplicates are discarded. This means the providers are found from your entire application, except when they are overridden on components or lazy loaded modules. Lazy loaded modules get their own root scope.
If the red module is lazy loaded then component 3 will get the provider from this module, if it's eagerly loaded the providers of the red module are added to the root scope of the application and component 3 will get it from there. This is all related to providers added by #NgModule()
Providers added to #Component() or #Directive() are not hoisted to any root scope. DI looks from the component that has a dependendency on its parent and its parents parent, ... for a provider and at last in the root scope of the application or in the root scope of the lazy loaded module if the component is part of one.
Related
I have two angularjs modules. First module contains some services, components, styles, directive, providers and so on. Second module has its own services, components, directives and so.
I need only the styles, services and providers from module First in module Second. If i add First as a dependency in Second, whole module will load along with all components, which is not needed in Second.
So is there a way by which i can inject selectively i.e. services and providers from module First in Module Second without loading all unnecessary components and other code in angularjs 1.x ?
I am loading module dynamically using system JS in Angular 6 and using angular-cli very similar to below link.
(Load new modules dynamically in run-time with Angular CLI & Angular 5)
Dynamically loaded module creates it's child injector and create new instance of services rather than using services from root injector.
As per Angular doc, lazy loaded module creates its own child injector and instances of service. In order to avoid that (for singleton), they suggest to create forRoot static method in lazy loaded module and import it in app module.
But in my case, as I am loading module at run time, I can't import LazyLoadedMOdule.forRoot() at bootstrap. I get to know which module is going to get loaded only at run time.
Can you please suggest to keep services singleton and use it in dynamically loaded module ?
For services that need to be singletons throughout the entire app, the recommended approach is to define those in a core module, which is then imported in the root module (AppModule).
You can refer to the Angular Style Guide for more detailed information.
I have a stackblitz books module with the following paths:
export const routes: Routes = [
{ path: 'find', component: FindBookPageComponent },
{ path: ':id', component: ViewBookPageComponent },
{ path: '', component: CollectionPageComponent },
];
Within the same app there is also a root injected AuthGuard service sitting in the services folder. If that guard is added to any of the routes in the books module, the application spins forever and will not launch.
Can a root injected CanActivate guard be used in a module without specifying it in the providers array of the module?
The documentation I have looked at show the guards being registered with the AppComponent providers, so I assumed that it would be ok to root inject the guards?
If it has to be specified, can it still be root injected?
The AuthGuard depends on a StateService and if it cannot be root injected, then neither can the StateService I assume?
Can a root injected CanActivate guard be used in a module without specifying it in the providers array of the module?
Technically it already is specified. The #Injectable({provideIn: 'root'}) tells the Angular compiler to add it to the main module's list of providers. It is just a convenience feature that was added to Angular.
The root module is the one defined by platformBrowserDynamic().bootstrapModule(module) in your entry TypeScript file (it's usually named main.ts).
If that guard is added to any of the routes in the books module, the application spins forever and will not launch.
Sounds like the AuthGaurd returns an observable that does not complete. Try adding a return observable.pipe(first()); to the guard function.
All of the router features that use observables (canActive, canLoad, resolve, etc.) require that the observables complete.
The documentation I have looked at show the guards being registered with the AppComponent providers, so I assumed that it would be ok to root inject the guards?
In this case, the module is the same as the one where the root routes are declared. You can see that RouterModule.forRoot(appRoutes) is used to import the router with the top-level routes. So anything defined in the providers at this level will be seen by the router, because they both share the same injector.
The AuthGuard depends on a StateService and if it cannot be root injected, then neither can the StateService I assume?
If a provider has not been declared when the injector tries to instantiate a class, then you get a run-time error that the constructor has an unknown injectable. It's a message that looks something like this error: constructor(?) where the question mark is the unknown parameter.
This means that if StateService is defined as a provider later, then AuthGaurd will just fail to be started. This depends upon when AuthGaurd is first used, because they are not created until they are first used.
I am working to an Angular 2 application which has only one module with several services registered as providers at bootstrap. The application has also several components in the same module which are using these services.
All components are receiving the services using DI (services are declared as parameters in construtor).
The problem that I am facing is: the same service is correctly injected in some components but in other no.
I checked the Injector object for the components where the injection fails and the services instances are within the Injector object. What it fails is the searching with the Injector.get() method. The token provided as parameter is different from the token stored in the Injector dictionary. I am not able to find out why because I am using the class name of the service and the token is automatically generated by the Javascript engine from this class name.
I tried to use OpaqueTokens to control the injection and, again, even if I am using the same opaque token, when Injector.get() method is called, the token provided by argument is considered different from the token stored by injector.
A short fragment from the code looks like
myservice.service.ts
import { OpaqueToken } from '#angular/core'
export const BACKEND = new OpaqueToken('backend');
app.module.ts
import { BACKEND } from 'myservice.service'
...
providers: [{ provide: BACKEND, useValue: "TestValue" }]
mycomponent.component.ts
import { Component, Inject } from '#angular/core'
import { BACKEND } from 'myservice.service'
#Component(
{
selector:'mycomponent'
templateUrl: ...
})
export class MyComponent
{
constructor(#Inject(BACKEND) config: string) {
console.info(JSON.stringify(config));
}
}
This injection with BACKEND token works for some components but for other not (the empty {} object is injected). I tried to debug the Javascript code in browser and it looks like the Injector is called with a different token than the token from dictionary (they are looking the same, with the same name 'backend' but still different and === returns false).
Note: I am using Angular 2.4.2, Typescript 2.1 with 'commonjs' modules and 'node' module resolution. systemjs is used as module loader.
Please, can you provide some ideeas about how to fix this?
Thanks,
Do you have various declarations of providers from different modules/components? For every declaration inside the providers array, angular will create a new instance of these services.
Do you have included the parentheses in the #Injectable decorator? https://angular.io/docs/ts/latest/guide/dependency-injection.html#!#injectable
NGmodule gives a way to organise our code in modules.
It also proposes to provides some services as a property of the module and I dont understand the way it works.
First does it mean that if I add a service in the provides property of a NGmodule, it will expose the service (meaning that I have to call the module inside of another one to use its services ) ?
And so, is there a NGmodule injector level ?
How Can I use a service outside the module box in another module ?
Providing a service in a modules means that the service will be instantiated and made available to all components, directives, and pipes that are part of the module. The word 'instantiation' is key here - since services are singletons, a module must keep track of services for every components, directive, or pipe that uses them. You could also provide the service in individual components, but that would instantiate the service each in each component, effectively negating the reason why we would use a singleton service in the first place. Providing it at the module-level solves this problem for us.
If you want to make your service available outside of your module, then don't have to do anything. Providing the service within a module that is imported in your project means that it's already available anywhere in your project.
Related: Why is a service provided in a feature module available everywhere?
If you wanted to make components, directives, or pipes available outside of your module, then you have to export them (and import them in the module where you want to use them). You can do that by using the export keyword in your module.
Related: What classes should I export?
For example, you can use the NgIf directive because it's exported from the CommonModule (docs), which we then import in our own modules:
#NgModule({
declarations: [COMMON_DIRECTIVES, COMMON_PIPES],
exports: [COMMON_DIRECTIVES, COMMON_PIPES],
providers: [
{provide: NgLocalization, useClass: NgLocaleLocalization},
],
})
Fun fact: if you only have one module (the root module that's bootstrapped), then you would actually only use the BrowserModule instead of the CommonModule. The reason all of the functionality of CommonModule is in BrowserModule is because BrowserModule just imports and the re-exports the entire CommonModule.
There's a great in-depth guide to the module system on angular2's website if you want more info. The FAQ page that I linked before is also super useful.