I have a lazy load module which needs to expose providers, so I am using the forRoot convention and returning the following code:
#NgModule({
imports: [RouterModule.forChild([
{path: "", component: LazyComponent},
])],
declarations: [LazyComponent],
})
export class LazyModule {
static forRoot() {
return {
ngModule: LazyModule,
providers: [provider]
};
}
}
The problem is when I invoke the forRoot in my app module the lazy load does not work anymore. ( I don't see the separate chunk in my console )
#NgModule({
declarations: [
AppComponent,
HelloComponent
],
imports: [
BrowserModule,
AppRoutingModule,
LazyModule.forRoot() <======== this stops the lazy load module
],
bootstrap: [AppComponent]
})
export class AppModule {
}
From what I learned it should only make the providers singleton, why it does not work?
As of right now, it's not possible to execute a forRoot (or any other configuration static method of this sort) in a module that will load lazily. The problem here is that such a method returns a ModuleWithProviders while loadChildren requires a NgModuleFactory.
I faced a similar issue where I had to lazy load a module with some configurations. So, I came up with this temporary fix.
Lazy Module
#NgModule({
imports: [
RouterModule.forChild([
{path: "", component: LazyComponent},
])],
declarations: [LazyComponent],
})
export class LazyModule {
// considering the function only handle providers
static setDynamicProviders(provider?: any) {
if(LazyModule && LazyModule.__annotations__ && LazyModule.__annotations__[0]){
LazyModule.__annotations__[0].providers.push(provider);
}
return LazyModule;
}
}
Parent Module
const someService = {
provide: 'SOME_TOKEN',
useFactory: (dependecy1) => (//dome something with dependency1)
deps: [SOME_DEPENDENCY_TOKEN]
}
#NgModule({
imports: [
RouterModule.forRoot([
path: 'lazy',
loadChildren: ()=>(import('*your path to lazy module*').then(ref => ref.LazyModule.setDynamicProvider(someService)))
])
]
})
So, I am just modifying the annotations created by #NgModule Decorator. At this stage, Angular does not support lazy loading with static methods that return ModuleWithProviders.
When you import a LazyModule in your AppModule imports array it is not "lazy" anymore. A lazy module should only be referenced in a dedicated RoutingModule.
So if I understood you correctly you would like to share a Service between your LazyModules?
If so remove LazyModule from AppModule and create a SharedModule and move your Service you like to share inside the providers array in SharedModule. Import SharedModule in your AppModule with forRoot and import your SharedModule without forRoot in your LazyModules
Try below code when we define a module is Lazy RouterModule.forChild:
#NgModule({
imports: [RouterModule.forChild([
{path: "", component: LazyComponent},
])],
declarations: [LazyComponent],
})
export class LazyModule { }
But when we load in the parent module, try the below code:
#NgModule({
declarations: [
AppComponent,
HelloComponent
],
imports: [
BrowserModule,
AppRoutingModule,
RouterModule.forRoot([
{
path: '',
loadChildren: './layout/layout.module#LayoutModule',
canActivate: [AuthGuard]
}
]) <======== right way to load lazy module
],
bootstrap: [AppComponent]
})
export class AppModule {
}
Note: FullPath is like './moduleFolder/lazy.module#LayoutModule'
LayoutModule is your exported Module Name
Please let me know
Related
I have a structure like this:
home - page
home2 - page
test-component - component
I need to be able to use the component(<app-test-component></app-test-component>) on both pages.
If I just use the tag:
ERROR Error: Uncaught (in promise): Error: Template parse errors:
'app-test-component' is not a known element:
If I just import (import {TestComponentComponent} from '../test-component/test-component.component') into home.module.ts and put it in the module import:
core.js:9110 ERROR Error: Uncaught (in promise): Error: Unexpected
directive 'TestComponentComponent' imported by the module
'HomePageModule'. Please add a #NgModule annotation.
A similar error if added to declarations.
Git.
How to import a component on both pages and use?
There are few changes that you should make:
You should not import component as I can see in the code.
Rearrange the project structure, and move reusable component such as TestComponent into a shared component. To give you an example, I'll give below example where I have created shared module names as TestModule
/app
/home
-home.component.ts
-home.module.ts
/test
-test.component.ts
-test.module.ts
-app.component.ts
-app.module.ts
app.module.ts
#NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HomeModule, RouterModule.forRoot([{ path: '', component: HomeComponent }])],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
test.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { TestComponent } from './test.component';
#NgModule({
declarations: [TestComponent],
exports: [TestComponent],
imports: [CommonModule],
})
export class TestModule {}
home.module.ts
#NgModule({
declarations: [HomeComponent],
exports: [HomeComponent],
imports: [
CommonModule,
TestModule
]
})
export class HomeModule { }
This set of project structure would certainly help you to reuse test.module features. Let me know if you need more code.
Declare TestComponentComponent only in home.module.
home.module.ts
#NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
RouterModule.forChild([
{
path: '',
component: HomePage
}
])
],
declarations: [HomePage, TestComponentComponent]
})
export class HomePageModule {}
app.module.ts
#NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
I have created a custom pipe that uses the DecimalPipe transform() method. I am using this pipe inside one of feature modules and I have to add both of those pipes to providers: [] (because MyCustomPipe uses DecimalPipe), like so:
index.ts:
#NgModule({
imports: [
MaterialModule,
SharedModule
],
declarations: [
...
],
providers: [
DecimalPipe,
MyCustomPipe
...
My goal however is to not have to add DecimalPipe to a feature module in this way and have that dependence between MyCustomPipe and DecimalPipe 'hidden', so that who ever is consuming MyCustomPipe can just worry about importing MyCustomPipe from SharedModule. I tried to resolve this by trying to follow the SharedModule pattern and have the DecimalPipe exported from SharedModule (as I did with MyCustomPipe), like so:
shared.module.ts:
...import { DecimalPipe } from '#angular/common';
...export * from '../pipes/index';
#NgModule({
imports: [
CommonModule,
FormsModule,
HttpModule,
DecimalPipe
],
declarations: [
LoginComponent,
ErrorComponent,
MyCustomPipe,
],
exports: [
CommonModule,
HttpModule,
LoginComponent,
ErrorComponent,
DecimalPipe,
MyCustomPipe
]
})
However, when I try to do this I get the error "Error: (SystemJS) Unexpected pipe 'DecimalPipe' imported by the module 'SharedModule'. Please add a #NgModule annotation." . Now, I could add DecimalPipe to declarations: [] in SharedModule, but then I get the error warning me that DecimalPipe is declared both in SharedModule and CommonModule. I think this stems from my lack of understanding of the SharedModule pattern described in the docs. I am not 100% if this even is the right approach, as I have never tried to share a custom pipe that uses a build-in Angular pipe with feature modules.
You don't have to worry about importing/declaring the inbuilt DecimalPipe along with your customPipe that uses it when you use/reuse it elsewhere in your app. Just declare the customPipe only. In your custom pipe's definition, just import the DecimalPipe like
import { DecimalPipe } from '#angular/common';
Then in the feature module that uses it just define them as part of the declarations array in #NgModule. If importing this feature module elsewhere in other feature module is supposed to identify this particular pipe being used in that new feature module, then mention this customPipe also part of the exports array declaration of the earlier feature module (that is being reused).
#NgModule({
imports: [
CommonModule,
...
],
declarations: [
CustomPipe
],
exports: [
CustomPipe // <-- do this only if you need this pipe to be available external to this ReusedFeatureModule when this module is 'imported' to other feature modules
]
})
export class ReusedFeatureModule { }
CustomPipe
import { Pipe, PipeTransform } from '#angular/core';
import { DecimalPipe } from '#angular/common';
#Pipe({
name: 'customPipe'
})
export class CustomPipe implements PipeTransform {
...
private originalDecimalPipe: DecimalPipe; // this variable will have the inbuilt DecimalPipe inside the constructor body
constructor () {
this.originalDecimalPipe = new DecimalPipe('en-US'); // pass the current locale as the argument
}
transform(value: any): any { return <transformed-value>;} // implement your transform
}
You should add the pipe under declarations not under import
#NgModule({
imports: [
CommonModule,
FormsModule,
HttpModule
],
declarations: [
LoginComponent,
ErrorComponent,
MyCustomPipe,
DecimalPipe
],
exports: [
CommonModule,
HttpModule,
LoginComponent,
ErrorComponent,
DecimalPipe,
MyCustomPipe
]
})
Updated
Just for safe note: add pipe-class in declarations and pipe-module in imports
#NgModule({
declarations: [
CustomDatePipe
],
imports: [
CustomDateModule
]
})
export class AppModule { }
I have an Angular module that I'm loading from an NPM module. How do I load the routes from it? Here is a snippet from my app.module.ts file:
import { HelloWorldModule } from 'hello-world-app-npm/hello-world-app.umd.js'; // The module loaded from NPM
const routes = [
{ path: 'hw', loadChildren: () => HelloWorldModule },
{ path: '', component: AppComponent },
]
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HelloWorldModule,
FormsModule,
HttpModule,
RouterModule.forRoot(routes)
],
exports: [RouterModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Here is how I've define my hello world module:
import { NgModule } from '#angular/core';
import { RouterModule } from '#angular/router';
import { HelloWorldComponent } from './hello-world.component';
export const routes = [{ path: '', component: HelloWorldComponent }]
#NgModule({
bootstrap: [HelloWorldComponent],
declarations: [
HelloWorldComponent
],
imports: [
RouterModule.forChild(routes)
]
})
export class HelloWorldModule {
}
When I go to "/hw" I get an error in chrome saying
Uncaught (in promise): RangeError: Maximum call stack size exceeded
What am I doing wrong? How do I fix this?
I suspect it could be because of two things.
HelloWorldModule should not be appear inside AppModule import.
bootstrap: [HelloWorldComponent] inside HelloWorldModule is unnecessary.
I have a case, where I have two modules, who have there own Pipe-definitions. One Module is a customer module, the other a product module. In products I want to use customer pipes and vice versa.
If modules in Angular 2 import each other like the following:
Example Root Module:
#NgModule({
imports: [
BrowserModule, AModule, BModule
],
declarations: [
AppComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Example Module B:
#NgModule({
imports: [BModule],
declarations: [
],
exports: []
})
export class AModule { }
Example Module A:
#NgModule({
imports: [AModule],
declarations: [
],
exports: []
})
export class BModule { }
The browser throws an error:
Uncaught Error: Unexpected value 'undefined' imported by the module
'BModule'
I could create a SharedModule, that declares both Pipes and import that module into A and BModule. But this would not be SOLID anymore, because customer pipes and product pipes just belong into there own module and not in another shared module.
Any proposals?
I googled, but did not find anything :(
I am currently using Angular 2 - RC5 with the router 3.0.0 RC1. This seems to be a pretty common error, but I can't find any solution that works. My component structure includes a "BasicContentComponent" which includes the main menu and the header and an aux outlet for the content of the child route. The "BasicContentComponent" comes from a shared module and the child routes component from specific module of that child route.
My route configuration looks like this
export const routeConfig = [
{
path: 'home',
component: BasicContentComponent,
children: [
{
path: '',
component: HomeContainer,
//canActivate: [IsAuthenticatedGuard],
outlet: 'content', // REMOVE THIS LINE
//resolve: {
// homeState: HomeResolver
//}
}
]
}
];
If I remove the "children" definition I am able to load "/home" but with this configuration I get the error.
Following are the module configurations, since the problem may also lie there.
app.module.ts
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
RouterModule.forRoot(routeConfig), //see above
SharedModule.forRoot(),
HomeModule
],
bootstrap: [AppComponent]
})
export class AppModule {}
shared.module.ts
import { Store, StoreModule } from '#ngrx/store';
#NgModule({
imports: [
RouterModule,
CommonModule,
...
StoreModule.provideStore(reducers),
],
declarations: [
BasicContentComponent,
...
],
exports: [BasicContentComponent, ...],
})
export class SharedModule {
static forRoot() : ModuleWithProviders {
return {
ngModule: SharedModule,
providers: [
MdIconRegistry,
...
IsAuthenticatedGuard,
...
HomeResolver
]
}
}
}
home.module.ts
#NgModule({
imports: [CommonModule, SharedModule],
declarations: [
HomeContainer,
HomeComponent
],
exports: [HomeContainer]
})
export class HomeModule { }
I get the following error
browser_adapter.js:84 EXCEPTION: Error: Uncaught (in promise): Error:
Cannot match any routes: 'home'
Any idea what the problem could be? Is it a route config or a modules issue? thanks
Edit
I forgot the templates:
app.component.html
<div>
<router-outlet></router-outlet>
</div>
basic-content.component.html
<md-sidenav-layout fullscreen>
<md-sidenav mode="side" align="start" [opened]="isOpened$ | async" color="warn">
<mainnav-container></mainnav-container>
</md-sidenav>
<page-header-container></page-header-container>
<div class="app-content">
<router-outlet name="content"></router-outlet> // REMOVE NAME
</div>
</md-sidenav-layout>
I got help on Gitter. If I remove the "name" attribute of the router-outlet and also remove it from the routerConfig everything works as intended. I don't fully understand why I have to remove it and how exactly the router finds the correct outlet - it makes kinda sense since the outlets are nested - but I will udpate my answer at a later point when the documentation for named outlets is more complete.
Thanks to #DzmitryShylovich on Gitter.