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

Lifecycle states seem inconsistent across platforms #1192

Open tsonevn opened 6 years ago

tsonevn commented 6 years ago

From @prolink007 on June 15, 2017 18:37

It seems the lifecycle states are not fired in the same order on android and ios. And also do not appear to be in a logical order.

For example:

import {AfterViewInit, Component, OnInit} from "@angular/core";
import {NavigatedData, Page} from "tns-core-modules/ui/page";
import {
    ApplicationEventData, exitEvent, launchEvent, on, resumeEvent,
    suspendEvent
} from "tns-core-modules/application";

@Component({
    selector: "lifecycle",
    moduleId: module.id,
    template: `
        <GridLayout (loaded)="onPageLoaded()" columns="auto" rows="auto">
            <Label id="label1" col="0" row="0"></Label>
        </GridLayout>
    `
})
export class Lifecycle implements OnInit, AfterViewInit {

    private page: Page;

    constructor(page: Page) {
        this.page = page;
        this.page.on("navigatingTo", this.onNavigatingTo.bind(this));
        this.page.on("navigatedTo", this.onNavigatedTo.bind(this));
        this.page.on("navigatingFrom", this.onNavigatingFrom.bind(this));
        this.page.on("navigatedFrom", this.onNavigatedFrom.bind(this));
        on(suspendEvent, this.onPause.bind(this));
        on(resumeEvent, this.onResume.bind(this));
        on(launchEvent, this.onStart.bind(this));
        on(exitEvent, this.onStop.bind(this));
    }

    public ngOnInit(): void {
        console.log("ngOnInit");
    }

    protected onPageLoaded(): void {
        console.log("onPageLoaded");
    }

    public ngAfterViewInit(): void {
        console.log("ngAfterViewInit");
    }

    protected onNavigatingTo(arg?: NavigatedData): void {
        console.log("onNavigatingTo");
    }

    protected onNavigatedTo(arg?: NavigatedData): void {
        console.log("onNavigatedTo");
    }

    protected onNavigatingFrom(arg?: NavigatedData): void {
        console.log("onNavigatingFrom");
    }

    protected onNavigatedFrom(arg?: NavigatedData): void {
        console.log("onNavigatedFrom");
    }

    protected onPause(arg?: ApplicationEventData): void {
        console.log("lifecycle onPause");
    }

    protected onResume(arg?: ApplicationEventData): void {
        console.log("lifecycle onResume");
    }

    protected onStart(arg?: ApplicationEventData): void {
        console.log("lifecycle onStart");
    }

    protected onStop(arg?: ApplicationEventData): void {
        console.log("lifecycle onStop");
    }
}

Android fire order:

  1. ngOnInit
  2. ngAfterViewInit
  3. onPageLoaded
  4. onNavigatedTo

iOS fire order:

  1. onPageLoaded
  2. ngOnInit
  3. ngAfterViewInit

iOS does not seem to get onNavigatedTo.

I would also assume that onPageLoaded should never be called before ngAfterViewInit. My impression of ngAfterViewInit is the contents of the page is fully loaded and ready. So onPageLoaded should be called at the same time as ngAfterViewInit.

Keep in mind that my onPageLoaded is on the top element of the template. The name seems a little confusing. I just named it that for testing. What we are really talking about here is the loaded callback for elements in the template.

Copied from original issue: NativeScript/NativeScript#4398

tsonevn commented 6 years ago

Hi @prolink007, Thank you for reporting this case, I review this scenario and all events seem to be fired in the same order for both iOS and Android except the loadedEvent. Firing the loaded event is something, which depends on the specifics of the platform. On iOS it will be fired at the beginning, however, on Android, the native component will be created and added to the visual tree a little bit later. This difference caused this behavior with the loaded event.

In case you need to an event which is fired at the same time, you could use ngOnInit orngAfterViewInit

tsonevn commented 6 years ago

From @NathanaelA on June 16, 2017 17:39

@tsonevn - Shouldn't this be marked as a bug and fixed? That is why we have a framework to create consistencies between the platforms.

  1. This is also a issue for those using a PAN app vs a NAN app; there is no ngOnInit or 'ngAfterViewInit` in PAN.
  2. Finally the events were fired consistently in order in v2 of the framework; so I this is a (imho major) regression in the v3 framework. See: http://fluentreports.com/blog/?p=191
tsonevn commented 6 years ago

From @prolink007 on June 16, 2017 19:47

@tsonevn Thank you for the information. However, iOS firing the loaded event after ngAfterViewInit seems like a bug to me. ngAfterViewInit is defined as "initialized the component's views and child views". And it is not. This seems like a bug to me and makes ngAfterViewInit unreliable.

tsonevn commented 6 years ago

From @vakrilov on June 20, 2017 9:0

I agree that event firing in a different order is a great source of confusion so I will try to clarify.

Angular Hooks

The angular hooks are related to the angular views and are guaranteed to execute in a particular order. This is order is the same for NS and Web projects. The note "initialized the component's views and child views", however views in this context are actually the angular components and views. So, it is safe to rely on ngAfterViewInit to resolve @ViewChildren queries.

NativeScript Events

The navigatingTo, loaded, navigatedTo events are part of the NativeScript views lifecycle and are also guaranteed to execute in this order for each page. The recommendation is to access the action nativeElement (ex. UIButton or android.widget.Button) in the loaded event when it is available.

The Problem

I see two problems here. Let's try to define them and decide on how best to approach this issue.

Both types are shuffled in a different ways in both platforms.

This is because the lifecycle of the native views is different for Android and IOS. In IOS native views can be created any time, but in Android you need the activity context(which is available only when the view is added to its parent) to call a nativeView's constructor.

The rule the thumb here is - "if you want to access the nativeViews - use loaded. If you want to access angular components or ViewChildren - use the angular hooks.".

I'm guessing that the real problem is - what happens when I need both ViewChildren and native views in my app in a same method? Is there something I'm missing?

The onNavigatedTo is not fired in IOS

Probably the event is fired before you attach to it in the component constructor. This happens only for the startup component. Again - can you elaborate that is the scenario, and decide if these events are the most appropriate for it.

tsonevn commented 6 years ago

From @NathanaelA on June 20, 2017 9:14

@vakrilov - I can't really comment on the Angular per-sey, but onNavigatedTo should always fire after loaded event, as the NavigatedTo event is the last event you can hook into in a PAN app for when you need to have the entire dom tree created to do anything with... So if this is firing before the loaded event that is a major regression from v2.... The event order should be Navigating, Loaded, Navigated...

tsonevn commented 6 years ago

From @speigg on January 5, 2018 4:22

I was expecting the new createNativeView function to be called before the "navigatedTo" event on iOS, but I guess "navigatedTo" is supposed to happen before createNativeView (on iOS)