Angular 2 injector hierarchy and NgModule - javascript

I wonder how NgModule actually affects Angular 2 injector hierarchy.
What does the hierarchy look like in an app with nested modules? Does it create a new injector instance per each module or gets access to top-level injector (similarly to Angular 1.x modules)?
It can be somewhat confusing to figure out the tree in a big app.
Is there a way to print out, inspect or explore visually the hierarchy of injectors (like it could be done for scope hierarchy in Angular 1.x)?

According to the Modules documentation: https://angular.io/docs/ts/latest/guide/ngmodule.html
Angular registers these providers with the root injector of the module's execution context. That's the application's root injector for all modules loaded when the application starts.
Angular can inject one of these provider services into any component
in the application. If this module provides the HeroService, or any
module loaded at launch provides the HeroService, Angular can inject
the same HeroService intance into any app component.
A lazy loaded module has its own sub-root injector which typically is
a direct child of the application root injector.
Lazy loaded services are scoped to the lazy module's injector. If a
lazy loaded module also provides the HeroService, any component
created within that module's context (e.g., by router navigation) gets
the local instance of the service, not the instance in the root
application injector.
Components in external modules continue to receive the instance
created for the application root.
So, you have one injector that is shared between all the modules. However, lazy-loaded components will have a child injector

You should be able to investigate providers using ng.probe($0) in the browser console. See also https://stackoverflow.com/a/35233711/217408.
You can load modules with MyModule.forRoot() (see also https://angular.io/docs/ts/latest/guide/ngmodule.html#!#providers) to get providers added at the root level.
If you provide it in a module, then it's available to the components in this module. I haven't seen it mentioned that a module introduces a child injector explicitly but to me this looks like it has to be the case.

Related

Nested modules lazy loading angular

In my project, I have below module hierarchy.
And I have shared module which will be shared between Module A and all its children.
Q: Do I have to import shared module in every child module of Module A or importing it in Module A is enough?
Thanks,
You have to import it in each child module separately, if that module will use at least one item from the Shared module.
That being said, you should consider not having Shared module at all, since it's a bad practice. Imagine you have 20 items in your Shared module, and some of you child modules will use only one item from it. Well, you will have to import the whole Shared module (even you will use only one item from it), which will increase the final bundle for your child module and decrease the performance.

Angular 8 normal loading module

I am working on an angular application that has 3 modules, root module auth module, and dashboard module. In my root module routing file I load auth and dashboard module using load children approach however dashboard module almost always load first when the user is logged in so I don't want to lazy load my dashboard module.
I search a day and looking for a solution I looked in the angular document to find a way to normal load a module however the only things I found was Angular switch from lazyLoading to 'normal' loading question which the answer doesn't work in aot compile production mode and I got this error
Error: Runtime compiler is not loaded
So how can I normal load my dashboard module in angular?
If you dont want lazy loading, remove the route corresponding to the load children from the root routing module. Instead place the import statement of the feature module(in your case auth and dashboard module) before the root routing module import. Angular will compile your feature module since it is in the imports array(not lazy load). Since the order matters, the routes in the feature module will be hit first rendering the feature modules' components.
I suggest you to read the documentation for routing and navigation from the official docs

NestJS: Using forRoot / forChild in custom module - race condition?

Repo is available here to highlight the issue.
I am having a problem with a race condition. I have created a ConfigModule - this has a forRoot and a forChild.
The forRoot sets up the loading of the .env file and the forChild uses it in another module.
The problem is that forChild is called before forRoot. The ConfigService would be injected with missing config because forRoot hasn't executed first.
> AppModule > ConfigModule.forRoot InstanceModule >
> ConfigModule.forChild
I placed some simple console.log commands that output this
I am in Config Module FOR CHILD
I am in Config Module FOR ROOT
As you can see the forChild is being executed first, I tried using forwardRef and that didn't work.
If you let the application run you will see
[2019-03-24T11:49:33.602] [ERROR] ConfigService - There are missing mandatory configuration: Missing PORT
[2019-03-24T11:49:33.602] [FATAL] ConfigService - Missing mandatory configuration, cannot continue!, exiting
This is because I check that some process.env are available which are loaded in via dotenv. Of course, because the forRoot isn't executed first then the forChild returns its own new instance of the ConfigService.
ConfigService validates the availability of the environment variables.
So, basically, the forChild is executing and returning its own ConfigService before forRoot.
TO make it work, if you comment out the InstanceModule inside the AppModule then it will automatically start listening and returns the port number from an environment variable.
Of course, because InstanceModule uses the forChild - there is a race condition.
Why does this not work?
1) Nest builds up a dependency graph and instantiates the given modules and their providers according to this graph. The order of your imports or the naming of your dynamic modules's methods (forRoot/forChild) does not influence the order of the instantiation.
2) When you create dynamic modules, each module will be its own instance, they won't be singletons like regular modules. In your case, you'd create two different ConfigModule instances and with it, two different ConfigService instances; hence they won't share your .env configuration. This can't work, independent of the instantiation order.
Alternatives
Have a look at the nestjs/typeorm package. Under the hood, it creates a shared TypeOrmCoreModule, which is shared between the different dynamic module instances created by TypeOrmModule.forRoot / TypeOrmModule.forChild. For it to be shared and dynamic at the same time, it has to be made #Global though. In your case, since you don't have any configurations in your forChild imports, you would just make the whole ConfigModule global and then omit the forChild() imports, since the ConfigService would be globally available anyway.
If you don't want your service to be globally available, you could initialize your service after the startup process, for example in your AppModule's onModuleInit method.

Angular 2 and rxjs subject in service isn't being update on component

Hi to all and thanks for your help in advance...
I'm building an app and I want to have a service that is used to handle some configurations and it's shared by different modules.
I really don't know if this is the best approach, but I'm using this since angularJS.
Config Service
sidebarCompress: Subject<boolean> = new Subject<boolean>();
public sidebarCompressed: Observable<boolean> = this.sidebarCompress.asObservable();
constructor() {}
compressSidebar(val: boolean = false) {
this.sidebarCompress.next(val);
// this is firing when used
console.log('changed on service');
}
Sidebar component
constructor(
private conf: ConfigService,
) {
conf.sidebarCompressed.subscribe(
sidebarCompressed => {
// this is not firing
console.log('changed on sidebar component');
});
}
any other component
constructor(
private conf: ConfigService,
) { }
ngOnInit() {
// The idea is to use it in a fn (not always on init)
this.conf.compressSidebar(true);
}
So, the Sidebar component subscription is not working, anyone knows what I am doing wrong here or if I should take a different approach!?
Again, Many thanks.
If you provide a service in #NgModule(), you get a single instance for the whole application, except when you also provide it in a #Component() or in #NgModule() of a lazy loaded module.
Providers provided in #NgModule() are hoisted into the applications root injector where the import order of modules matters, and modules listed later in imports: [] override previous modules providers if the provider keys match. Providers added in AppModules #NgModule() directly override providers of imported modules.
Lazy loaded modules get their own "root scope", which is a child injector of the application root injector.
I assume your problem is caused by providing the service in a non-lazy-loaded and lazy loaded module, which results in 2 instances.
Components or services that inject this module get either the instance from the application root, if they are part of modules that are non-lazy-loaded, or from the lazy loaded module when they are part of the lazy loaded module.
As solution, remove the provider from the lazy loaded module.
You can also implement forRoot() and import MyLazyLoadedModule.forRoot() in the AppModule to get the service of the lazy loaded module into the applications root injector and to avoid duplicate service instances.

Cannot inject AuthenticationController

I am trying to inject a controller into my app.run function, however i keep getting:
Uncaught Error: [$injector:unpr] http://errors.angularjs.org/1.2.10/$injector/unpr?p0=AuthenticationControllerProvider%20%3C-%20AuthenticationController
Here's my app.js:
var app = angular.module('app', [
'AuthenticationModule'
]);
app.run(['$rootScope', 'AuthenticationService', 'AuthenticationController',
function($rootScope, AuthenticationService, AuthenticationController) {
console.log(AuthenticationController);
}
]);
The AuthenticationService is injecting just fine. Why are AuthenticationController not being injected?
As stated in the AngularJS documentation on modules:
Run blocks - get executed after the injector is created and are used
to kickstart the application. Only instances and constants can be
injected into run blocks. This is to prevent further system
configuration during application run time.
In the documentation for controllers, it states:
In Angular, a Controller is a JavaScript constructor function that is
used to augment the Angular Scope. When a Controller is attached to
the DOM via the ng-controller directive, Angular will instantiate a
new Controller object, using the specified Controller's constructor
function.
A controller is an instance constructor function, not an instance itself, as opposed to a service, which is. Therefor, from what I can gather, controllers cannot be injected into a run block.
If you need to configure a controller at start-up time, then use a provider. As it turns out, in angular, controllers (along with directives, filters, and animations) are all simply syntactic sugar for a provider. Providers can be configured using configuration blocks: configuration block documentation
Configuration blocks - get executed during the provider registrations
and configuration phase. Only providers and constants can be injected
into configuration blocks. This is to prevent accidental instantiation
of services before they have been fully configured.

Categories