NativeScript / nativescript-angular

Integrating NativeScript with Angular
http://docs.nativescript.org/angular/tutorial/ng-chapter-0
Apache License 2.0
1.21k stars 241 forks source link

Page.navigatedToEvent not called after async CanActivate guard #980

Open Burgov opened 7 years ago

Burgov commented 7 years ago

If your main route is guarded by a guard that runs async (e.g. does an Http call before completing the observable), the Page.navigatedToEvent is never called. Linked to that, the action bar shows some weird behavior in that the page title is not removed if it's overridden in the template.

In the example below, change .timer(300) to .of(true), and it will work as expected.

This only appears to happen in the initial navigation. Subsequent navigations appear to work as expected.

Route guard:

import {CanActivate} from "@angular/router";
import {Observable} from "rxjs";
export class Guard implements CanActivate {
    canActivate() {
        return Observable.timer(300).mapTo(true);
    }
}

AppModule:

import { NgModule, APP_INITIALIZER, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";

import { AppComponent } from "./app.component";
import {NativeScriptRouterModule} from "nativescript-angular";
import {HomeComponent} from "./home.component";
import {Observable} from "rxjs";
import {Guard} from "./guard.service";

var pages = [
  { path: "", component: HomeComponent, canActivate: [Guard] }
]

@NgModule({
  bootstrap: [AppComponent],
  declarations: [AppComponent, HomeComponent],
  imports: [NativeScriptModule, NativeScriptRouterModule, NativeScriptRouterModule.forRoot(pages)],
  schemas: [NO_ERRORS_SCHEMA],
  providers: [Guard]
})
export class AppModule {}

AppComponent:

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

@Component({
  selector: "my-app",
  template: `
    <page-router-outlet></page-router-outlet>
  `
})
export class AppComponent {
}

HomeComponent:

import {Component, OnInit} from "@angular/core";
import {Page} from "tns-core-modules/ui/page/page";

@Component({
    template: `
    <ActionBar title="My App" class="action-bar">
        <Label text="test"></Label>
    </ActionBar>

    <Label text="test"></Label>
  `,
})
export class HomeComponent implements OnInit {
    constructor(private page: Page) {
    }

    ngOnInit() {
        this.page.on(Page.navigatedToEvent, () => console.log('navto'))
    }
}

I tried to circumvent it by setting initialNavigation to false in the router, loading all the necessary data for the Guard in an APP_INITIALIZER and triggering initialNavigation from there, but that seems to yield the same behavior. In that case, this is the code:

AppModule:

import {NgModule, APP_INITIALIZER, NO_ERRORS_SCHEMA, Injector} from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";

import { AppComponent } from "./app.component";
import {NativeScriptRouterModule} from "nativescript-angular";
import {HomeComponent} from "./home.component";
import {Observable} from "rxjs";
import {Guard} from "./guard.service";
import {Router} from "@angular/router";

var pages = [
  { path: "", component: HomeComponent, canActivate: [Guard] }
]

@NgModule({
  bootstrap: [AppComponent],
  declarations: [AppComponent, HomeComponent],
  imports: [NativeScriptModule, NativeScriptRouterModule, NativeScriptRouterModule.forRoot(pages, {initialNavigation:false})],
  schemas: [NO_ERRORS_SCHEMA],
  providers: [Guard, {
    provide: APP_INITIALIZER,
    useFactory: (injector: Injector) => () => {
      Observable.timer(300).do(() => {
        injector.get(Router).initialNavigation();
      }).toPromise();
    },
    deps: [Injector],
    multi: true
  }]
})
export class AppModule {}

Guard:

import {CanActivate} from "@angular/router";
import {Observable} from "rxjs";
export class Guard implements CanActivate {
    canActivate() {
        return Observable.of(true);
    }
}

image This screenshot shows how the text "test"doesn't replace the app title, but is appended to it.

Burgov commented 6 years ago

I've made a playground app that demonstrates it:

https://play.nativescript.org/?template=play-ng&id=gWyFG7&v=3

The output is "no", while expected is "yes".

in guard.ts, if you comment the return line and uncomment the other, you will get the expected "yes" output

NickIliev commented 6 years ago

@Burgov thank you for reporting this issue - confirming this as unexpected behavior when using the page events for the initial page.. It seems that the issue is only reproducible with larger timeout settings (setting the timer to 100ms is not reproducing the issue).

Steps to reproduce - use this Playground and change the timer settings in guard.ts file