Skip to content

Is possible to use dynamic routing(lazy loading) feature of Angular2 in NativeScript + Angular2 #533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
NickIliev opened this issue Nov 14, 2016 · 10 comments
Labels

Comments

@NickIliev
Copy link

NickIliev commented Nov 14, 2016

From @wantaek on November 14, 2016 8:42

Did you verify this is a real problem by searching [Stack Overflow]

Yes

Tell us about the problem

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

Which platform(s) does your issue occur on?

I test in iOS. It may occur both.

Please provide the following version numbers that your issue occurs with:

  • CLI: (run tns --version to fetch it)
    2.4.0
  • Cross-platform modules: (check the 'version' attribute in the
    node_modules/tns-core-modules/package.json file in your project)
    2.4.0
  • Runtime(s): (look for the "tns-android" and "tns-ios" properties in the
    package.json file of your project)
    2.4.0
  • Plugin(s): (look for the version number in the package.json file of your
    project)

Please tell us how to recreate the issue in as much detail as possible.

When 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

Is there code involved? If so, please share the minimal amount of code needed to recreate the problem.

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

@NickIliev
Copy link
Author

NickIliev commented Nov 14, 2016

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.

@fricker
Copy link

fricker commented Nov 23, 2016

@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?

@NickIliev
Copy link
Author

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)

@fricker
Copy link

fricker commented Nov 25, 2016

@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.

@NickIliev
Copy link
Author

@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.

@astrobob
Copy link

astrobob commented Dec 7, 2016

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;
    }
  }  

}

@NickIliev
Copy link
Author

NickIliev commented Dec 7, 2016

Hey @astrobob you can use canActivateChild

For example, I have tried it in our nativescript-sdk-examples-ng application like this:

  • create the guard (I've placed mine in the root dir as auth.ts)
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");
  }
}
  • I've tested it for ActionBar examples like this:
    in app.routes.ts added the canActivateChild
import { AuthGuard } from "./auth";
c
    {
        path: "action-bar",
        loadChildren: () => require("./ui-category/action-bar/action-bar-examples.module")["ActionBarExamplesModule"],
        canActivateChild: [AuthGuard],
        data: { title: "ActionBar" }
    },
  • and register the guard as providers for the respective NgModule in this case in main.ts
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)

@astrobob
Copy link

astrobob commented Dec 9, 2016

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

@otaran
Copy link

otaran commented Feb 8, 2017

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 nativescript-angular@1.4.1.

@gimox
Copy link

gimox commented Dec 4, 2017

@otaran remember to import NgModuleFactoryLoader

import {NgModuleFactoryLoader} from "@angular/core";

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants