Angular4 ng-content gets built when ngIf is false

Cabadath picture Cabadath · Jul 5, 2017 · Viewed 12.9k times · Source

I have a problem with the new ng-content transclusion.

Let's say I have a component my-component that, in its ngOnInit() function does some heavy operation on load (for now, just a console.log()).

I have a wrapper, that displays the content via transclusion (my-wrapper.component.html).

<ng-content></ng-content>

If I set the surroundings up like this, the log statement doesn't show:

<my-wrapper *ngIf="false">
    <my-component></my-component>
</my-wrapper>

I assume, the my-wrapper component does not get built, so the content is ignored.

But if I try to move the logic into the my-wrapper component like this (my-wrapper.component.html):

<ng-container *ngIf="false">
    <ng-content></ng-content>
</ng-container>

I always see the console.log() output. I guess, the my-component gets built and then stored away until the *ngIf becomes true inside my-wrapper.

The intention was to build a generic "list-item + detail" component. Say I have a list of N overview-elements (my-wrapper), that get rendered in a *ngFor loop. Every of those elements has its own detail component (my-component) that is supposed to load its own data, once I decide to show more infos to a specific item.

overview.html:

<ng-container *ngFor="let item of items">
    <my-wrapper>
        <my-component id="item.id"></my-component>
    </my-wrapper>
</ng-container>

my-wrapper.component.html:

<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail">
    <ng-content></ng-content>
</div>

Is there a way to tell Angular, to ignore the transcluded content until it is necessary to be added to the page? Analogously to how it was in AngularJS.

Answer

Cabadath picture Cabadath · Jul 6, 2017

Based on the comment of @nsinreal I found an answer. I find it to be a bit abstruse, so I'm trying to post it here:

The answer is to work with ng-template and *ngTemplateOutlet.

In the my-wrapper component, set up the template like this (my-wrapper.component.html):

<div (click)="toggleDetail()">Click for more</div>
<div *ngIf="showDetail" [hidden]="!isInitialized">
    <ng-container *ngTemplateOutlet="detailRef"></ng-container>
</div>

Note, that the [hidden] there is not really necessary, it hides the "raw" template of the child until it decides it is done loading. Just make sure, not to put it in a *ngIf, otherwise the *ngTemplateOutlet will never get triggered, leading to nothing happening at all.

To set the detailRef, put this in the component code (my-wrapper.component.ts):

import { ContentChild, TemplateRef } from '@angular/core';

@Component({ ... })
export class MyWrapperComponent {
    @ContentChild(TemplateRef) detailRef;

    ...
}

Now, you can use the wrapper like this:

<my-wrapper>
    <ng-template>
        <my-component></my-component>
    </ng-template>
</my-wrapper>

I am not sure, why it needs such complicated "workarounds", when it used to be so easy to do this in AngularJS.