Reputation: 150
I am struggling a lot to get a nice way of managing the children of a Component
when the children are not always created because of *ngIf
.
HTML structure of component RootComponent
:
<div id="root-div" *ngIf="dto">
<div id="nested-div-1" *ngIf="something">
<my-nested-component>
</my-nested-component>
</div>
<div id="nested-div-2" *ngIf="somethingElse">
<div #anotherChild>
</div>
</div>
<button (onClick)="setSomethingElse()">Button</button>
</div>
The problem:
RootComponent
is rendered.REST
call is made in the ngAfterViewInit
to get the dto
, directly the something
boolean is setted
@ViewChild() myNestedComponent : MyNestedComponent;
ngAfterViewInit() {
restService.get().then((result) => {
this.dto = result;
this.something = true;
}
}
Angular will now render the root-div
, nested-div-1
and my-nested-component
.
You will get a undefined
exception when you do the following:
@ViewChild() myNestedComponent : MyNestedComponent;
ngAfterViewInit() {
restService.get().then((result) => {
this.dto = result;
this.something = true;
this.myNestedComponent.theMethod();
}
}
This makes perfect sense, because Angular did not update the view yet, so myNestedComponent
is undefined
Bad/Hacky solutions
ChangeDetectorRef.detectChanges()
to force Angular to update the viewthis.myNestedComponent.theMethod()
in a setTimeout
, which delayes the execution of that line till Angular has done his cycle.Better solutions
Give my-nested-component
an @Output
event, so that my-nested-component
can execute a method in RootComponent
when he is done initializing (for example in ngAfterViewInit
of the my-nested-component
)
<my-nested-component (afterInit)="nestedComponentInitialized()"></my-nested-component>
nestedComponentInitialized() {
this.myNestedComponentIsInitalized = true;
this.myNestedComponent.theMethod(); // Works now!
}
The problem with this approach is that the RootComponent needs check the this.myNestedComponentIsInitalized
when he wants to do something with myNestedComponent
You can avoid this initialisation methods when you use the setter of the @ViewChild()
. But requires still some code for every @ViewChild
myNestedComponent: MyNestedComponent;
@ViewChild() set myNestedComponentSetter(myNestedComponent: MyNestedComponent) {
this.myNestedComponentIsInitalized = true;
this.myNestedComponent = myNestedComponent;
}
Change @ViewChild
into @ViewChildren
, so that is it possible to listen for changes of that list, which means the component is initalized
@ViewChildren() myNestedComponents : QueryList<MyNestedComponent>;
ngAfterViewInit() {
restService.get().then((result) => {
this.dto = result;
this.something = true;
this.myNestedComponents.changes.subscribe(() => {
this.myNestedComponentIsInitalized = true;
this.myNestedComponents[0].theMethod();
}
}
}
The problem with this approach is that you can only listen for changes on a list, which feels strange when you already know that there is only one MyNestedComponent
in your RootComponent
. The good thing about this is that you can subscribe on the event of initialisation.
These three 'better solutions' become a nightmare when you have for example 4 different nested components. Because this means that the RootCompoment
needs 4 initialisation methods (or 4 QueryList
's) with 4 booleans for the states of the nested components. Managing all of this becomes very complicated.
Don't use *ngIf
, but use [hidden]
so that everything exists always. This sounds good, but this means that everything is always rendered, and that you sometimes still to check if something exists. Because this will not possible (because dto.name
can raise an exception, because dto
is not initialized by the REST
call yet):
<div id="root-div" [hidden]="dto">
<div id="nested-div-1" [hidden]="something">
<my-nested-component>
</my-nested-component>
</div>
<div id="nested-div-2" [hidden]="!somethingElse">
<div #anotherChild>
<span>{{dto.name}}</span> <!-- EXCEPTION, dto is not there yet! -->
</div>
</div>
<button (onClick)="setSomethingElse()">Button</button>
</div>
The elegant solution?
I am looking for a solution that makes it possible to manage the states of components that are not always initalized because of *ngIf
conditions.
I would like to do things like:
my-nested-component
and anotherChild
are initialized.anotherChild
to my-nested-component
when anotherChild
is initalized (so dto
and somethingElse
were true), and my-nested-component
is also there (so something
was also true)What is the best pattern or approach to manage nested Component
s when they are not always there because of *ngIf?
Upvotes: 3
Views: 1212
Reputation: 2374
I think the best solution is to design your angular project with respect to the container/component approach.
This means you have containers (parents) which handle the data and children which just render based on the input values. All actions that cause updates to data should be emitted via output from children to its parent, the parent updates the data and via input the updated data are passed down to the children again.
So your data flow would be always like this: service->container->component.
This concept comes mostly from flux/redux pattern and react, but it provides a lot of advantages also in angular. The benefits are:
For Rest requests it is also nice to use the async pipe in combination with ngIf, your child gets rendered when all data is available. Take a look here: https://toddmotto.com/angular-ngif-async-pipe
A good guide for the container/component structure can be found here: https://www.thegreatcodeadventure.com/the-react-plus-redux-container-pattern/
And also here: https://blog.angular-university.io/angular-2-smart-components-vs-presentation-components-whats-the-difference-when-to-use-each-and-why/
I know it is huge topic and I cannot explain all its complexity within one post, but maybe it gives you some inspiration.
Upvotes: 4