nativescript-angular icon indicating copy to clipboard operation
nativescript-angular copied to clipboard

Native element not available in AfterViewInit on android

Open NickIliev opened this issue 7 years ago • 13 comments

From @prolink007 on June 15, 2017 15:44

The native element is not available inside AfterViewInit on Android but is available on ios.

For example:

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

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

    private _page: Page;

    constructor(page: Page) {
        this._page = page;
    }

    public ngAfterViewInit(): void {
        let label: Label = this._page.getViewById<Label>("label1");
        // label.android will be undefined on android
        console.log("ngAfterViewInit: " + label.android + label.ios);
    }
}

label.android will be undefined when running on android. label.ios is defined when running on ios.

I would expect EVERYTHING to be loaded and ready when ngAfterViewInit is fired. Because the view is supposed to be ready at that point.

I know you can subscribe to loaded on the element to know when it is finished loading, but i would expect AfterViewInit to be called after the entire "dom" is loaded and ready.

Copied from original issue: NativeScript/NativeScript#4396

NickIliev avatar Jun 16 '17 08:06 NickIliev

@prolink007 I can confirm that this inconsistency i is observed on Android. It is a timing issue and as a workaround, you can wrap your reference in 1 millisecond time out

e.g.

    public ngAfterViewInit(): void {
        
        setTimeout(() => {
            let label: Label = this._page.getViewById<Label>("label1");
            console.log("ngAfterViewInit Android: " + label.android + " iOS: " + label.ios);
        }, 1);

    }

Test project demonstrating the issue here (remove the setTimeout to reproduce the problem in Android)

NickIliev avatar Jun 16 '17 08:06 NickIliev

I confirm the problem too. I had to set a timeout of 50ms to resolve it, 1ms wasn't enough.

wandri avatar Jul 28 '17 20:07 wandri

Hi - @tsonevn @NickIliev :)

I have the same issue...But in my situation I need to do it with 500ms because I have a lot of info to load...and I am pretty sure that in slower and older device it probably won't be enough... There is a way to had a listener to when element is shown on page?

Thanks guys :)

bzaruk avatar Jan 10 '18 16:01 bzaruk

A similar issue can be observed when nativeElement.android is accessed from a directive as shown below. The @Input setter seems to be called before the android native component is initialized. Using the workaround given by @NickIliev works, but as @shabib3 states, this might not work on all devices.

@Directive({ 
    selector: '[appShadow]' 
})
export class ShadowDirective {
    constructor(private element: ElementRef) { }

    @Input() set appShadow(value: string) {
        // Following prints "ANDROID NOT INITIALIZED YET"
        this.setShadow(this.element.nativeElement, +value);

        // Following works and prints "ANDROID INITIALIZED"
        setTimeout(
            () => this.setShadow(this.element.nativeElement, +value)
            , 10
        );
    }

    private setShadow(view: any, elevation: number) {
        if (view.android) {
            console.log("ANDROID INITIALIZED");
            let backgroundColor = view.backgroundColor.android;
            let nativeView = view.android;
            nativeView.setBackgroundColor(backgroundColor);
            nativeView.setElevation(elevation);
        } else {
            console.log("ANDROID NOT INITIALIZED YET");
        }
    }
}

syntaxfoundry avatar Jan 23 '18 22:01 syntaxfoundry

Is there any update on this issue? I want to get the native view as it is loaded, but binding the loaded event breaks listviews (https://github.com/NativeScript/nativescript-angular/issues/1221) and using ngAfterViewInit just doesn't work on Android.

This also means plugins that bind the loaded event (like nativescript-ng-shadow, nativescript-ngx-shadow and nativescript-ng-ripple) just flat out break your app in some situations.

So i either have to add timeouts that might even break the code if a view is quickly created/destroyed, or add *ngIf everywhere potentially breaking component recycling.

edusperoni avatar Sep 12 '18 14:09 edusperoni

This seems to work under some conditions (perhaps not all):

import { Directive, ElementRef, OnInit } from "@angular/core";
import { fromEvent } from "rxjs";
import { take } from "rxjs/operators";
import { isIOS } from "tns-core-modules/platform";
import { Button } from "tns-core-modules/ui/button";

@Directive({
    selector: "Button",
})
export class ButtonDirective implements OnInit {

    constructor(private el: ElementRef) { }
    
    ngOnInit() {
      const button = <Button>this.el.nativeElement;
      fromEvent(button, "loaded").pipe(
        take(1)
      ).subscribe(_ => {
          if (isIOS) {
            (<UIButton>button.ios).titleLabel.lineBreakMode = NSLineBreakMode.ByTruncatingTail;
          } else {
            // android is available as well
            (<android.widget.Button>button.android)....
          }
      });
    }
}

NathanWalker avatar Jan 03 '19 22:01 NathanWalker

Note the above strategy only works for UI components that have a loaded event. Curiously Slider https://github.com/NativeScript/NativeScript/blob/master/tns-core-modules/ui/slider/slider.android.ts does not (for ios or android), not sure if intentional or just a small mistaken omission?

@EmilStoychev @vakrilov

NathanWalker avatar Jan 03 '19 23:01 NathanWalker

are there any updates to the topic? As of today the bug seems still to exist

cjohn001 avatar Apr 17 '20 23:04 cjohn001

@cjohn001 instead of using the top abstraction lifecycle events (meaning Angular and AfterViewInit) you should probably use the native elements loaded event - this way the native element will be 100% accessible. The thing is that Angular is the top abstraction and at the time when the top abstraction events are executed that doesn't mean that you will have the bottom ones finished as well - consider this a limitation.

NickIliev avatar Apr 24 '20 11:04 NickIliev

Hello Nicklliev, thanks for the feedback. I tried the loaded variant es well. Unfortunately it does not work for my use case. The loaded event is triggered separately for each component. In my use case I needed to compute a layout relative to other elements. If I am in a loaded callback event from one component I cannnot make asumptions that other components I need for my layout computation where already loaded. Hence hooking up to loaded events is not a solution. I worked around the issue now by hardcoding the layout. However, I think a livecycle hook for nativescript is here missing which is triggered once all components on the page were loaded. I found this article https://medium.com/developer-rants/the-curious-case-of-angular-lifecycle-hooks-in-nativescript-2be0386cb2fa which describes a workaround. However, for a good integration with Angular I think it would make much more sense to support the toplevel api.

cjohn001 avatar Apr 24 '20 11:04 cjohn001

I am in the same situation as @cjohn001

asciidiego avatar Sep 04 '20 14:09 asciidiego

I could use some guideline and I would be happy to try and fix this @NathanWalker @NickIliev

asciidiego avatar Sep 04 '20 14:09 asciidiego

Same problem, any update?

icefee avatar Jan 23 '24 08:01 icefee