Reputation: 2212
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
Reputation: 4173
With newer angular (9.x) I solved this problem by setting static to true:
@ViewChild('myContainer', {static: true})
Upvotes: 1
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
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
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
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