Nativescript: Issues with ngAfterViewInit

Created on 28 Jun 2016  路  10Comments  路  Source: NativeScript/NativeScript

I posted a question on StackOverflow (http://stackoverflow.com/questions/38084180/nativescript-issue-with-loading-view). The issue is basically that I cannot get or set properties of UI components when the page is loaded. I already tried to use ngAfterViewInit() or (loaded) from the template, but they don't seem to work at all.

This is my template page_2.html:

<StackLayout>
    <Label text="to page 1" class="button_label" (tap)="to_page1()"></Label>
    <Label #label_in_2 text="second label" class="test_label" (loaded)="whatisthewidth(label_in_2)"></Label>
</StackLayout>

This is the style file page_2-common.css:

Label.test_label {
    background-color: yellow;

    width: 75%;
    height: 20%;
}

And this is page_2.component.ts:

import {Component, ViewChild, ElementRef, OnInit} from "@angular/core";
import {Router} from "@angular/router-deprecated";

import {Page} from "ui/page";

import {Label} from "ui/label";

@Component({
    selector: "page2",
    templateUrl: "pages/page_2/page_2.html",
    styleUrls: ["pages/page_2/page_2-common.css", "pages/page_2/page_2.css"]
})

export class Page2 implements OnInit {

    @ViewChild("label_in_2") label_in_2: ElementRef;

    constructor(private _router: Router, private page: Page) {}

    ngOnInit() {

    }

    ngAfterViewInit() { // trial 1
        let label = <Label>this.label_in_2.nativeElement;

        console.log("width of the label 2 in ngAfterViewInit: " + label.getActualSize().width); // give me 0
    }

    whatisthewidth(testLabel) {

        console.log("width of the label 2 in whatisthewidth: " + testLabel.getActualSize().width); // give me 0
    }

    to_page1() {
        let label = <Label>this.label_in_2.nativeElement;

        console.log("width of the label 2: " + label.getActualSize().width); // give me the correct value

        console.log("to page 1");
        this._router.navigate(["Page1"]);
    }

}

How can I get or set properties of UI components from .ts files properly and correctly?

question

Most helpful comment

I don't know why issues here are closing without an exact solution or bug fix. just a temporary workaround is enough to close an issue which is not fixed actually? setTimeout is not an option with different templates and different devices!

All 10 comments

From your code you did not implement ngAfterViewInit try

import {Component, ViewChild, ElementRef, OnInit,AfterViewInit} from "@angular/core";
import {Router} from "@angular/router-deprecated";

import {Page} from "ui/page";

import {Label} from "ui/label";

@Component({
    selector: "page2",
    templateUrl: "pages/page_2/page_2.html",
    styleUrls: ["pages/page_2/page_2-common.css", "pages/page_2/page_2.css"]
})

export class Page2 implements OnInit, AfterViewInit{

    @ViewChild("label_in_2") label_in_2: ElementRef;

    constructor(private _router: Router, private page: Page) {}

    ngOnInit() {

    }

    ngAfterViewInit() { // trial 1
        let label = <Label>this.label_in_2.nativeElement;

        console.log("width of the label 2 in ngAfterViewInit: " + label.getActualSize().width); // give me 0
    }

    whatisthewidth(testLabel) {

        console.log("width of the label 2 in whatisthewidth: " + testLabel.getActualSize().width); // give me 0
    }

    to_page1() {
        let label = <Label>this.label_in_2.nativeElement;

        console.log("width of the label 2: " + label.getActualSize().width); // give me the correct value

        console.log("to page 1");
        this._router.navigate(["Page1"]);
    }

}

Unfortunately, implementing ngAfterViewInit didn't fix the problem. Thanks a lot for your advice though!

Hi @jiwonyune,
I reviewed your problem and found that you should implement ngAfterViewInit as @triniwiz suggested, however on other hand not all style properties are attached after ngAfterViewInit event. As possible solution you could use setTimeout method like this

        setTimeout(() => {
            console.log("size "+ container.getActualSize().width);
        }, 10);

and getActualSize will return correct value different than 0. You could also review the attached code below:

app.component.html

<StackLayout>
    <Label text="Tap the button" class="title"></Label>

    <Button text="TAP" (tap)="onTap()"></Button>

    <Label   #label [text]="message" class="test_label"  textWrap="true"></Label>
</StackLayout>

app.component.ts

import {Component, ViewChild, ElementRef, OnInit, AfterViewInit} from "@angular/core"; import {Label} from "ui/label"; import {View} from "ui/core/view"
import {setTimeout} from "timer"
@Component({
    selector: "my-app",
    templateUrl:"app.component.html",
    styleUrls: ["style.css"],
})
export class AppComponent implements OnInit, AfterViewInit {
    public counter: number = 16;

    @ViewChild("label") container: ElementRef;

    public get message(): string {
        if (this.counter > 0) {
            return this.counter + " taps left";
        } else {
            return "Hoorraaay! \nYou are ready to start building!";
        }
    }

    public onTap() {

        let container:Label = <Label>this.container.nativeElement;
        console.log("label text property "+container.getActualSize().width);
        this.counter--;
    }

    constructor(){

    }

    ngOnInit(){

    }


    ngAfterViewInit() {
        let container:Label = <Label>this.container.nativeElement;
       // container.onLoaded
        console.log("-----------------------Label---------------------");
        console.log("label text property "+container.text);
        console.log("width property "+container.width);
        setTimeout(() => {
            console.log("size "+ container.getActualSize().width);
            console.log("--------------------------------------------");
        }, 10);

    }
}

style.css

.test_label {
    background-color: yellow;

    width: 75%;
    height: 20%;
}

Regards,
@tsonevn

I think this is a bug, ngAfterViewInit only works with templates, and not templateUrl.

Or may depend on the template size, so the timeout is not a fixed time to wait.

As osamamourad noted, the timeout isn't fixed. I resolved this with a recursive function. I needed the height of a component with id 'logoContainer':

@ViewChild('logoContainer') private logoContainer : ElementRef

private _compHeight = 0;
private set compHeight(value){
    this._compHeight = value;
    if (value == 0){
        this.determineLogoContainerHeight();
    }
}
private get compHeight(){
    return this._compHeight;
}
private iteration = 0;
private determineLogoContainerHeight(){
    let container = this.logoContainer.nativeElement;

    this.iteration++;
    //we need a timeout for the size to come up with the real value
    setTimeout(() => {
        console.log("size "+ container.getActualSize().width);
        this.compHeight = container.getActualSize().width;
    }, this.iteration * 10);
}

ngAfterViewInit() {
    this.determineLogoContainerHeight();
}

The timeout increases each time the view is not completely ready to avoid to much load when everything is already slow.

I don't like it but it is a workaround.

I don't know why issues here are closing without an exact solution or bug fix. just a temporary workaround is enough to close an issue which is not fixed actually? setTimeout is not an option with different templates and different devices!

@tsonevn
I'm myself extremely disappointed with nativescript.
Things like this cost developers hours to days of work, no documentation, no solution to real and important problems.

This issue is still exactly the same after 2 years! And it gets closed and ignored.
You are unable to access any ui properties or functions in ngOnInit, ngAfterViewInit or even (and that is just purely ridiculous as it's not angular related) after the actual page loaded event.

inheriting ngAfterViewInit is of course not a solution as this has absolutely nothing to do with execution. People suggesting that should go back to the basics and figure out how typescript works (hint: there are no classes or inheritance in transpiled js that's actually executed). That a contributor is suggesting that is really (really) strange.

Adding a timeout is of course no stable solution for any production ready app, It's also not a workaround, unless you set that timeout to over a second and more.
If that is the solution everyone can go back to web apps because they'd be actually way faster if you have to introduce arbitrary delays like that.
setTimeout does nothing but wait for one frame to pass because after that first re render the attributes are available.
But in the worst case you do not know how long that frame takes. You can't just say "it's always 60fps, so 16ms timeout is working", especially on page init.

As this is also a problem with the page life cycle events this is a clear nativescript issue and should be looked at.

@ventr1x do you have a specific case you are trying to solve - if so you can post the sample project or Playground so we can take a look at the problem you are trying to solve.

Regarding this issue:

The Angular abstraction is on top of the NativeScript one. So if you need to access properties of any native mobile element use the NativeScript's lifecycle and not the Angular one. So instead of using ngAfterViewInit we can use loaded event on any NativeScript's view. This way you will be sure that all layouts have been rendered and measured.

For example
some.component.html

<StackLayout (loaded)="onStackLoaded($event)">
    <Label text="Some Label' (loaded)="onLabelLoaded($event)"></Label>
</StackLayout>

some.component.ts

onStackLoaded(args: EventData) {
    let myStack = <StackLayout>args.object; // using the NativeScript loaded event to get reference to your native element
}

onLabelLoaded(args: EventData) {
    let myLabel= <Label>args.object; // using the NativeScript loaded event to get reference to your native element
    console.log(myLabel.text);
}

On the loaded event a simple setTimeout seem to work more or less consistently.
Normally such workarounds are a result of angulars data binding and scope which is probably the case here too when using ViewChild instead of going for the event args.

I haven't tried going with the events args directly yet as it is pretty problematic if you need a few dimensions and have to wait for half a dozen events to be fired, track them and then start your code.
Does the parent loaded trigger directly or does it wait for the children to load (there we have a case of missing documentation)?

Better documentation for the underlying system would go a very long way. It would attract more plugin devs and more devs that actually develop more than a little hobby app that displays a few lists.
Yesterday I had to invest hours to figure out how to e.g. change the status bar.. which is in the end two lines of code. Not because I don't understand the native side, but because I wasn't aware how to actually access it correctly as the documentation has zero examples for this (the only example I found is how to create a NSArray...).

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

valentinstoychev picture valentinstoychev  路  79Comments

morningrat picture morningrat  路  67Comments

NickIliev picture NickIliev  路  58Comments

AbanoubNassem picture AbanoubNassem  路  53Comments

dbbk picture dbbk  路  54Comments