Gerco Brandwijk
Gerco Brandwijk

Reputation: 150

Best practice to manage child components in combination with *ngIf

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:

Bad/Hacky solutions

  1. Using ChangeDetectorRef.detectChanges() to force Angular to update the view
  2. Wrap this.myNestedComponent.theMethod() in a setTimeout, which delayes the execution of that line till Angular has done his cycle.

Better solutions

  1. 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

  2. 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;
    }       
    
  3. 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.

  1. 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:

TLDR

What is the best pattern or approach to manage nested Components when they are not always there because of *ngIf?

Upvotes: 3

Views: 1212

Answers (1)

J. S.
J. S.

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:

  • seldom use of viewchild (always a problem with undefined)
  • better use of ChangeDetection strategy (children can often use ChangeDetection.OnPush)
  • It forces to really think about the application structure
  • It result in more readable code and no overloaded components

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

Related Questions