I have a child ponent 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 ponent 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
}
I have a child ponent 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 ponent 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
}
Share
Improve this question
edited May 8, 2019 at 19:58
Kamil Naja
6,6926 gold badges37 silver badges52 bronze badges
asked May 8, 2019 at 19:21
RasMasonRasMason
2,2125 gold badges36 silver badges60 bronze badges
2
-
2
Why not using a setter? Like
@Input() set myUpdatingVar(val) {}
. Also it would be really helpful if you reproduced your problem on StackBlitz or similar. – Sergey Commented May 8, 2019 at 19:22 - 1 you cant' try to access viewchildren until the afterviewinit hook. You'll need to do a check in your onchanges hook to see if the ponent is initialized yet and skip if not, and also put in an initial run in the afterviewinit hook. That being said, there probably is a much more angular way to do this. – bryan60 Commented May 8, 2019 at 19:33
5 Answers
Reset to default 5Instead 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 ponent.
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!
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 acplishing this goal, unless you're interacting with some poorly integrated 3rd party lib.
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.
With newer angular (9.x) I solved this problem by setting static to true:
@ViewChild('myContainer', {static: true})
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
.