Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular4 ng-content gets built when ngIf is false

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.

like image 990
Cabadath Avatar asked Jul 05 '17 14:07

Cabadath


People also ask

Does ngIf false destroy component?

Does ngIf destroy component? Angular's ngIf directive does not simply hide and show. It creates and destroys an HTML element based on the result of a JavaScript expression.

What is * in * ngIf in Angular?

A shorthand form of the directive, *ngIf="condition" , is generally used, provided as an attribute of the anchor element for the inserted template. Angular expands this into a more explicit version, in which the anchor element is contained in an <ng-template> element.

Can we use ngIf in ng container?

ng-container s work just like that, and it also accepts Angular structural directives ( ngIf , ngFor , e.t.c). They are elements that can serve as wrappers but do not add an extra element to the DOM.

What is the difference between ngIf and hidden in Angular?

The main difference is that *ngIf will remove the element from the DOM, while [hidden] actually plays with the CSS style by setting display:none . Generally it is expensive to add and remove stuff from the DOM for frequent actions.


3 Answers

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.

like image 121
Cabadath Avatar answered Oct 24 '22 06:10

Cabadath


By doing this:

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

You are not calling MyComponent component, because the *ngIf is false. that means, that not calling it you are not instancing it and, therefore, not passing through its ngOnInit. And that's why you are not getting the console log.

By doing this:

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

You are inside the component, you are just limiting what to render in your template, but you already instanced your component and, therefore, you passed through your ngOnInit and you get your console log done.

If, you want to limit something (component call with selector or a ng-content or even a div) until you have some data available, you can do the following:

datasLoaded: Promise<boolean>;

this.getData().subscribe(
       (data) => {
            this.datasLoaded = Promise.resolve(true); // Setting the Promise as resolved after I have the needed data
       }
);

And in your template:

<ng-container *ngIf="datasLoaded | async">
   // stuff here
</ng-container>

Or:

<my-component *ngIf="datasLoaded | async">
   // Didn't test this one, but should follow the same logic. If it doesn't, wrap it and add the ngIf to the wrapper
</my-component>
like image 31
SrAxi Avatar answered Oct 24 '22 06:10

SrAxi


It’s because Ng content happens at the build time and when you pass the content it is actually not removed or recreated with the ngIf directive. It is only moved and the component is instantiated .

like image 1
anonymous Avatar answered Oct 24 '22 07:10

anonymous