I have a small angular application that makes use of the CoreModule and SharedModule pattern. Because
my application is quite small at the moment, I have imported the SharedModule in the root module, AppModule. I also have a lazy loaded admin module called AdminModule but it seems like it does not have access to shared module even though it has been imported in the root module. I have to explicitly import the SharedModule again in order to use it inside the components of the admin module. Why is this? If I have to re-import them again inside each lazy loaded module, what's the point of importing them in the root module?
AppModule
...
imports: [
BrowserModule,
BrowserAnimationsModule,
MatExpansionModule,
AppRoutingModule,
AngularFireModule.initializeApp(environment.firebase),
AngularFirestoreModule, // imports firebase/firestore, only needed for database features
AngularFireAuthModule, // imports firebase/auth, only needed for auth features,
AngularFireStorageModule, // imports firebase/storage only needed for storage features
SharedModule, // To be imported on each feature module, instead of AppModule. For now, this is fine though
CoreModule,
HttpClientModule
],
...
App routing module (Lazy loaded)
const routes: Routes = [
{
path: 'admin',
loadChildren: './admin/admin.module#AdminModule',
canActivate: [AdminAuthGuard]
}]
AdminModule
...
imports: [CommonModule, AdminRoutingModule, SharedModule, CoreModule],
An example is if I have a footer component inside the SharedModule and try to use it in my AdminHomeComponent which is a part of the AdminModule, I get an error (without importing the SharedModule). All works fine when I import it.
Importing a Module lets the components of the module that imports the external module, to be able to see the components from that imported module. But only the ones in that module, not in the nested ones.
Importing a module in the root module is useful for PROVIDERS, that's quite different from Components.
If you import a Module that provides a service in a module, all the components from that module and the nested modules will be able to see the providers. (this is the reason why it's a good practice to put the providers of a module only in a static forRoot() method if the module has providers and components that will be reused).
I also found this great answer on another post https://stackoverflow.com/a/43153529/8948458
The minute you create a new module, lazy or not, any new module and you declare anything into it, that new module has a clean state. It has no knowledge of anything,
Related
In order to make my AppModule cleaner, I imported an eagerly loaded feature module in the CoreModule, which is imported (once) in the AppModule.
What i've found interesting, is that the app works by either exporting or importing the feature module in the CoreModule. Can anyone explain the difference? What's the correct way?
Well, if there's a feature(mostly a declarable like a Component, Directive or Pipe) that you have in that FeatureModule and you want to use it in your CoreModule(which you've imported in your AppModule) or AppModule, you'll have to export it from your CoreModule as well.
If you export something from a Module it would be available to use in a Module that you import that Module in.
And if you export that module from the module that you've imported it in, you'll be able to use the exported module's features in the module that you're importing this module in.
Let's take for eg, an example with a CoreModule, an AppModule and a FeatureModule.
The FeatureModule has a FeatureComponent that's declared in it. If you want to use this FeatureComponent in the CoreModule, you'll have to export the FeatureComponent from the FeatureModule and then import the FeatureModule in your CoreModule.
Now if you want to use the FeatureCompoent in your AppModule as well. you can simply export the FeatureModule from the CoreModule. And since you've already imported the CoreModule in the AppModule you'll have access to all the exported members of the CoreModule and FeatureModule is one of them.
Here's what Angular's Docs say, to help you understand better:
The set of components, directives, and pipes declared in this NgModule that can be used in the template of any component that is part of an NgModule that imports this NgModule. Exported declarations are the module's public API.
A declarable belongs to one and only one NgModule. A module can list another module among its exports, in which case all of that module's public declaration are exported.
Declarations are private by default. If this ModuleA does not export UserComponent, then only the components within this ModuleA can use UserComponent.
ModuleA can import ModuleB and also export it, making exports from ModuleB available to an NgModule that imports ModuleA.
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'm developing an angular 7 application with lazy loaded modules. I'm using angular material components as well. I would like to localize and support multiple locales in datepicker component.
I would like to change it globally for the whole application when application language changes. It may be done via DateAdapter.setLocale method.
However the problem is that if I change it on my root module then it does not change in lazy loaded modules.
Yes, lazy loaded modules have their injector and receive their DateAdapter, where locale is not set to the correct one.
How to achieve that DateAdapter is singleton in the whole app? For other modules there is a forChild() vs forRoot() API, but not for datepicker module.
The problem for me was that I imported my DateAdapter in my SharedModule and then imported that SharedModule in my AppModule and in other LazyModules.
To make the DateAdapter a singleton service you should provide the DateAdapter you're using only once at root level in your application and not at root level and in LazyModules.
To do so make sure that you import the module that provides the DateAdapter, e.g. MatNativeDateModule, only in your AppModule (or another root module thats imported only once like a CoreModule). Don't import MatNativeDateModule in multiple modules or in a module that's imported multiple times like a SharedModule.
import { MatNativeDateModule } from '#angular/material/core'
#NgModule({
declarations: [
AppComponent,
],
imports: [
MatNativeDateModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
Then you can set the locale in your AppComponent.
export class AppComponent implements OnInit {
constructor(private dateAdapter: DateAdapter<Date>) {}
ngOnInit(): void {
this.dateAdapter.setLocale('en')
}
}
And the same DateAdapter with the set locale will be injected in your LazyModules.
I don't know what I'm asking would still be called tree-shaking, but suppose I have two modules:
CoreModule: {
declarations: [Component_One, Component_Two, Component_Three],
exports: [Component_One, Component_Two, Component_Three]
}
AppModule: {
imports: [CoreModule]
}
In my app I am only ever using Component_One. The whole reason I import CoreModule is so I can use Component_One.
All three components exposed in CoreModule are totally independent of each other.
If I build the app with three shaking enabled, Component_Two and Component_Three will still be included in the final bundle, correct? Because they are imported by the CoreModule, even though they are totally irrelevant to my final app and not used anywhere.
So is there currently a way to tree shake them out of the final bundle?
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.