Skip to content

Native element not available in AfterViewInit on android #848

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

Open
NickIliev opened this issue Jun 16, 2017 · 13 comments
Open

Native element not available in AfterViewInit on android #848

NickIliev opened this issue Jun 16, 2017 · 13 comments

Comments

@NickIliev
Copy link

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
Copy link
Author

NickIliev commented Jun 16, 2017

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

@wandri
Copy link

wandri commented Jul 28, 2017

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

@bzaruk
Copy link

bzaruk commented Jan 10, 2018

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 :)

@syntaxfoundry
Copy link

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

@edusperoni
Copy link
Collaborator

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 (#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.

@NathanWalker
Copy link
Contributor

NathanWalker commented Jan 3, 2019

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
Copy link
Contributor

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

@cjohn001
Copy link

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

@NickIliev
Copy link
Author

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

@cjohn001
Copy link

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.

@asciidiego
Copy link

I am in the same situation as @cjohn001

@asciidiego
Copy link

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

@icefee
Copy link

icefee commented Jan 23, 2024

Same problem, any update?

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

No branches or pull requests

10 participants