_From @wantaek on November 14, 2016 8:42_
Yes
I want to use dynamic routing(lazy loading) feature of Angular2 in NativeScript + Angular2
This is a dynamic routing(lazy loading) of Angular2 sample source.
http://plnkr.co/edit/fBqrEpqafiVSB2Vvapzb?p=preview
I use this source to my NativeScript + Angular2 project.
But when i try to navigate 'lazy' page, error occurred.
CONSOLE ERROR file:///app/tns_modules/nativescript-angular/zone.js/dist/zone-nativescript.js:344:22: Error: Uncaught (in promise): ReferenceError: Can't find variable: System
I test in iOS. It may occur both.
tns --version to fetch it)node_modules/tns-core-modules/package.json file in your project)"tns-android" and "tns-ios" properties in thepackage.json file of your project)package.json file of yourWhen I try to navigate to 'lazy' page, error ocurred:
CONSOLE ERROR file:///app/tns_modules/nativescript-angular/zone.js/dist/zone-nativescript.js:344:22: Error: Uncaught (in promise): ReferenceError: Can't find variable: System
import {Component} from '@angular/core';
import {Router} from '@angular/router';
@Component({
selector: "my-app",
// templateUrl: "app.component.html",
template: `
<StackLayout>
<StackLayout class="nav">
<Button text="First"
[nsRouterLink]="['/']"></Button>
<Button text="Second"
[nsRouterLink]="['/app']"></Button>
<Button text="Lazy"
[nsRouterLink]="['/lazy']"></Button>
</StackLayout>
<router-outlet></router-outlet>
</StackLayout>
`
})
export class AppComponent {
loaded: boolean = false;
constructor(private router: Router) {}
ngOnInit() {
let routerConfig = this.router.config;
if (!this.loaded) {
routerConfig.unshift({
path: `lazy`,
loadChildren: 'app/portal.module#PortalModule'
});
console.log('/app route (Portal) added', JSON.stringify(routerConfig));
this.router.resetConfig(routerConfig);
this.loaded = true;
}
}
}
_Copied from original issue: NativeScript/NativeScript#3085_
Hey @wantaek
Yes, you can use lazy loading for routes - We are currently working on the lazy loading implementation in this branch of the nativescript-sdk-examples-ng. Once our implementation is ready, tested and clean we will update the master branch as well and you can use it as a reference for out project.
I will update the info here once we have a final solution.
@NickIliev, I saw that there was a commit (#530) to support lazy loading for the page-router-outlet. My use case is for lazy loading components using the standard router-outlet. It seems that @wantaek indicated that this was his use case also based on the example template he included even though he mentioned 'lazy' page. Will this also be supported?
Hey @fricker
Yes, you can already use router-outlet with lazy loading.
For example, if we take the nativescript-sdk-examples-ng application and look where the page-router-outlet is used you can replace it with router-outlet and again use the lazy loading for the inner examples.
e.g in app.component.ts.:
instead of this:
<GridLayout>
<page-router-outlet></page-router-outlet>
</GridLayout>
you can change it like this:
<StackLayout>
<StackLayout class="nav">
<Button text="animations" [nsRouterLink]="['/animations']"></Button>
<Button text="button" [nsRouterLink]="['/button']"></Button>
</StackLayout>
<router-outlet></router-outlet>
</StackLayout>
Both animations and button are sections which leads to three more sub-sections for which we are using lazy loading. If you open _app/ui-category/button_ you will notice a module file and a component file . In button-example.module.ts we are lazy loading the childrens of Button example like this:
import { NgModule } from "@angular/core";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { NativeScriptModule } from "nativescript-angular/platform";
import { ButtonExamplesComponent } from "./button-examples.component";
import { ButtonBindingTextComponent } from "./binding-text/binding-text.component";
import { ButtonTapEventComponent } from "./tap-event/tap-event.component";
import { ButtonTextComponent } from "./text/text.component";
import { NativeScriptFormsModule } from "nativescript-angular/forms";
import { TitleAndNavButtonModule } from "../../directives/title-and-nav-button.module";
export const routerConfig = [
{
path: '',
component: ButtonExamplesComponent
},
{
path: 'binding-text',
component: ButtonBindingTextComponent,
data: { title: "Binding text" }
},
{
path: 'tap-event',
component: ButtonTapEventComponent,
data: { title: "Tap event" }
},
{
path: 'text',
component: ButtonTextComponent,
data: { title: "Text" }
}
];
@NgModule({
imports: [TitleAndNavButtonModule, NativeScriptModule, NativeScriptRouterModule, NativeScriptFormsModule, NativeScriptRouterModule.forChild(routerConfig)],
declarations: [ButtonExamplesComponent, ButtonBindingTextComponent, ButtonTapEventComponent, ButtonTextComponent]
})
export class ButtonExamplesModule {
constructor() { }
}
Note that here we are using forChild method with NativeScriptRouterModule.
The final part where we are actually loading those children is placed in app.routes.ts.
Instead of cheating there paths for all of your component we are using loadChildren
{
path: "button",
loadChildren: () => require("./ui-category/button/button-examples.module")["ButtonExamplesModule"],
data: { title: "Button" }
},
Again the whole architecture is virtually the same for page-router-outlet and for router-outlet except for the actual place where you are placing the outlets (in our case the app.component.ts)
@NickIliev,
Thank you very much for providing some much needed clarity to how loadChildren is supported in nativescript-angular. I was not aware Angular router's loadChildren could be configured using a call to require wrapped in a function. The Angular documentation that I have been referencing makes no mention of this. Additionally, there is no mention in the NativeScript documentation that loadChildren was supported or how it needs to be configured.
When I tried using the string syntax indicated in Angular's docs and got the same System not found error that @wantaek encountered. I even tried installing webpack to see if that might work. Now that I know that this alternate loadChildren configuration exists I went back to the web implementation of my app and changed it to use the wrapped require() implementation to maintain some consistency.
BTW, your link to the nativescript-sdk-examples-ng repo in your initial reply to @wantaek is broken. I'll definitely be using that project to supplement the NativeScript docs going forward.
@fricker Good to know that I've managed to help you guys! I have updated the link to our sample app - the lazy load is now implemented in the master branch.
Hi NickIliev,
Thank you for your post, it works perfectly
However, I tried to add "canActivate" as follow:
const APP_ROUTES: any = [
{ path: '', redirectTo: '/client', pathMatch: 'full'},
{ path: 'secure', loadChildren: () => require("./secure/secure.module")["SecureModule"], canActivate: [AuthGuard]},
{ path: 'signin', component: SigninComponent },
];
And it doesn't seem to work. As loadChildren is called asynchronously in your example, canActivate seems to resolve before and although the redirect I have in my AuthGuard component is fired correctly, the loadChildren seems to bypass my redirection. Any idea how to make it work?
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService,
private router: Router) {}
canActivate(): Observable<boolean> | boolean {
if (!this.authService.isAuthenticated()) {
this.router.navigate(['/signin']);
return false;
} else {
return true;
}
}
}
Hey @astrobob you can use canActivateChild
For example, I have tried it in our nativescript-sdk-examples-ng application like this:
import { CanActivateChild, RouterStateSnapshot, ActivatedRouteSnapshot } from "@angular/router";
import { Injectable } from "@angular/core";
import { Observable as RxObservable } from "rxjs";
export class UserToken {}
export class Permissions {
canActivate(user: UserToken, id: string): boolean {
return false; // or return true if you want to activate the route
}
}
@Injectable()
export class AuthGuard implements CanActivateChild {
constructor(private permissions: Permissions, private currentUser: UserToken) {}
canActivateChild(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): RxObservable<boolean>|Promise<boolean>|boolean {
return this.permissions.canActivate(this.currentUser, "action-bar");
}
}
import { AuthGuard } from "./auth";
c
{
path: "action-bar",
loadChildren: () => require("./ui-category/action-bar/action-bar-examples.module")["ActionBarExamplesModule"],
canActivateChild: [AuthGuard],
data: { title: "ActionBar" }
},
import { AuthGuard, UserToken, Permissions } from "./auth";
@NgModule({
declarations: [
// >> (hide)
AppComponent,
// << (hide)
],
bootstrap: [AppComponent],
imports: [
NativeScriptModule,
NativeScriptFormsModule,
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot(routes),
],
providers: [AuthGuard, UserToken, Permissions]
})
note that the above implementation is defaulted to return false (the route is not activated)
Hi @NickIliev
Thank you very much! It works perfectly! In didn't know about CanActivateChild
For those who want to see the code difference, I put below:
in my app.router:
const APP_ROUTES: any = [
{ path: '', redirectTo: '/client', pathMatch: 'full'},
{ path: 'secure', loadChildren: () => require("./secure/secure.module")["SecureModule"], canActivateChild: [AuthGuard]},
{ path: 'signin', component: SigninComponent },
];
in my auth.guard.ts (that I import in my app.router.ts)
import { Injectable } from "@angular/core";
import { CanActivateChild, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
import { Observable as RxObservable } from "rxjs/Rx";
import { AuthService } from "./auth.service";
@Injectable()
export class AuthGuard implements CanActivateChild {
constructor(private authService: AuthService) {}
canActivateChild(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): RxObservable<boolean>|Promise<boolean>|boolean {
return this.authService.isAuthenticated();
}
}
Cheers
For those of you who googled for Angular Router lazy loading and is wondering why you can't just use string-based routes like loadChildren: './secure/secure.module#SecureModule', well... you can! Just inject NSModuleFactoryLoader from nativescript-angular/router in your AppModule:
import { NSModuleFactoryLoader } from 'nativescript-angular/router';
@NgModule({
...
providers: [
{ provide: NgModuleFactoryLoader, useClass: NSModuleFactoryLoader }
]
})
export class AppModule { }
NSModuleFactoryLoader is available since at least [email protected].
@otaran remember to import NgModuleFactoryLoader
import {NgModuleFactoryLoader} from "@angular/core";
Most helpful comment
For those of you who googled for Angular Router lazy loading and is wondering why you can't just use string-based routes like
loadChildren: './secure/secure.module#SecureModule', well... you can! Just injectNSModuleFactoryLoaderfromnativescript-angular/routerin yourAppModule:NSModuleFactoryLoaderis available since at least[email protected].