RasMason
RasMason

Reputation: 2212

template variable always undefined ngOnChanges

I have a child component that is used twice within its parent, it uses ngOnChages to update values;

This all worked fine, but now I'm trying to manipulate the template, within the child, according to information passed it via @Input variables.

What I'm finding is; when the parent component is first loaded, the template variables are undefined, within a function call

This makes sense because this function is called within the onChanges method and does not give the template enough time to load.

I need to refactor my code, but how?

At the moment it works if I use setTimeout

So to simply:

@ViewChild('myContainer', {read: ElementRef}) myContainer: ElementRef;
@Input() myUpdatingVar

ngOnChanges(): void {
  if (this.myUpdatingVar === someValue) {
      this.myFunc(this.someValue1);
   } else {
      this.myFunc(this.someValue2);
   }
}

myFunc(param){
   do stuff....
   this.updateDom(anotherParam);
}

updateDom(param) {
// If I use setTimeout this works
this.myContainer.nativeElement.querySelector(`#someId`); // Undefined
}

Upvotes: 1

Views: 6466

Answers (5)

Spikolynn
Spikolynn

Reputation: 4173

With newer angular (9.x) I solved this problem by setting static to true:

@ViewChild('myContainer', {static: true})

Upvotes: 1

Torab Shaikh
Torab Shaikh

Reputation: 456

Instead of using ngOnChanges, You can detect changes using a setter.

Here is what you need to do

@Input()
set myUpdatingVar(value: any) {
   if(value) {
        this.myFunc(value);
   }
}

It will not only load when initializing the view but also Whenever something is changed in the myUpdatingVar, Angular will run setter and execute your function. Also, ngOnChanges is an expensive lifecycle hook. If you don't use it carefully, It will run everytime something is changed in the component.

But if you want to use ngOnChanges, you should use changes it detects in the variable instead of using the variable itself.

Here is the example

ngOnChanges(changes: SimpleChanges): void {
  if (changes.myUpdatingVar.currentValue === someValue) {
      this.myFunc(changes.myUpdatingVar.currentValue);
   } else {
      this.myFunc(changes.myUpdatingVar.currentValue);
   }
}

You can check if it is the first change and can also get currentValue and previousValue which is useful in this case.

I hope my answer helped you somehow. Happy Coding!

Upvotes: 5

rjustin
rjustin

Reputation: 1439

Checkout this guide: https://angular.io/guide/lifecycle-hooks

Angular lifecycle events fire in a specific order and some happen before the dom elements are loaded.

ngAfterViewInit is the hook that lets you know that the DOM(or at least the abstraction you see) is ready to be queried. This means template variables and viewchildren among other things wont work properly before this event has triggered.

EDIT: it sounds like you understand the timing. To fix this I suggest checking that the template variable is defined before using it in onChanges(now it will happen on all subsequent calls to onChanges) then do the same thing in ngAfterViewInit if you need to run it once at the beginning.

Upvotes: 1

bryan60
bryan60

Reputation: 29335

As others have said, onchanges runs too early for accessing viewchildren, you'll need something like this:

@ViewChild('myContainer', {read: ElementRef}) myContainer: ElementRef;
@Input() myUpdatingVar

ngOnChanges(): void {
  if (this.myContainer) {
    if (this.myUpdatingVar === someValue) {
       this.myFunc(this.someValue1);
     } else {
       this.myFunc(this.someValue2);
     }
   }
}

ngAfterViewInit() {
  this.ngOnChanges();
}

myFunc(param){
   do stuff....
   this.updateDom(anotherParam);
}

updateDom(param) {
// If I use setTimeout this works
this.myContainer.nativeElement.querySelector(`#someId`); // Undefined
}

This way you skip the logic if the view hasn't been initialized and you manually trigger a run of ngOnChanges after the view is initialized.

All this said, there's probably a far more angular way of accomplishing this goal, unless you're interacting with some poorly integrated 3rd party lib.

Upvotes: 1

user11390576
user11390576

Reputation:

For DOM manipulation, you should use the ngAfterViewInit lifecycle hook, to ensure the template has loaded into the DOM.

ngOnChanges fires once early, even before ngOnInit. Far too early for DOM manipulation.

You can use an isViewInitialized Boolean and set it to true in ngAfterViewInit, and then use it in an if condition in ngOnChanges.

Upvotes: 0

Related Questions