htmlangularangular-ng-ifng-templatengtemplateoutlet

Why does [ngIf] on a template not apply when plugged into an outlet?


To clarify, I am aware of the deprecation status on the NgIf directive, but I am still interested in finding an answer for the issue. In my example, the [ngIf] does not apply when used with an outlet thus the template (top right text) is wrongfully rendering. I would like to know if this might be a bug or a syntax misuse and if there is another way than replacing [ngIf] with *ngIf, which would resolve this.

a.component.html

<app-b>
  <ng-template #topRightText [ngIf]="false">
    <span>Top right text</span>
  </ng-template>
</app-b>

b.component.ts

@ContentChild('topRightText') topRightTextTemplateRef: TemplateRef<any>;

b.component.html

<ng-container [ngTemplateOutlet]="topRightTextTemplateRef"></ng-container>

I expected that [ngIf], the long-hand syntax of *ngIf, applies the same when applied on a template that is used in an outlet. When replacing [ngIf] with *ngIf, the boolean is correctly used for conditional rendering, but why? Isn't [ngIf] just the long-hand syntax equivalent? Like explained here: https://angular.dev/api/common/NgIf#shorthand-syntax


Solution

  • Reason:

    So my understanding is that the content inside the long syntax <ng-template #topRightText [ngIf]="false"> is what is hidden and shown, but the binding template where you place the [ngIf] is still visible. Hence you see the content being rendered.


    Analysis:

    An reasonable explanation is found in the Angular documentation of ngIf:

    NgIf Docs - Angular.dev

    Simple form with shorthand syntax:
    <div *ngIf="condition">Content to render when condition is true.</div>
    

    Simple form with expanded syntax:

    <ng-template [ngIf]="condition">
      <div>Content to render when condition is true.</div>
    </ng-template>
    

    So as you can see, when we use a structural directive syntax *ngIf it is wrapped on another ng-template with the long syntax, which works fine with content projection.


    Thus applying the same principle on the long syntax of your example. We can observe the content is hidden and shown as expected.

    <div>case [ngIf] false</div>
    <app-child>
      <ng-template [ngIf]="false">
        <ng-template #topRightText>
          <span>Top right text</span>
        </ng-template>
      </ng-template>
    </app-child>
    <div>case [ngIf] true</div>
    <app-child>
      <ng-template [ngIf]="true">
        <ng-template #topRightText>
          <span>Top right text</span>
        </ng-template>
      </ng-template>
    </app-child>
    <div>case *ngIf</div>
    <app-child>
      <ng-template #topRightText *ngIf="false">
        <span>Top right text</span>
      </ng-template>
    </app-child>
    

    Full Code:

    import { Component, ContentChild, TemplateRef } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import { CommonModule } from '@angular/common';
    
    @Component({
      selector: 'app-child',
      imports: [CommonModule],
      template: `
        <ng-container [ngTemplateOutlet]="topRightTextTemplateRef"></ng-container>
      `,
    })
    export class Child {
      @ContentChild('topRightText') topRightTextTemplateRef!: TemplateRef<any>;
    }
    
    @Component({
      selector: 'app-root',
      imports: [Child, CommonModule],
      template: `
        <div>case [ngIf] false</div>
        <app-child>
          <ng-template [ngIf]="false">
            <ng-template #topRightText>
              <span>Top right text</span>
            </ng-template>
          </ng-template>
        </app-child>
        <div style="margin-top:50px;">case [ngIf] true</div>
        <app-child>
          <ng-template [ngIf]="true">
            <ng-template #topRightText>
              <span>Top right text</span>
            </ng-template>
          </ng-template>
        </app-child>
        <div style="margin-top:50px;">case *ngIf</div>
        <app-child>
          <ng-template #topRightText *ngIf="false">
            <span>Top right text</span>
          </ng-template>
        </app-child>
      `,
    })
    export class App {
      name = 'Angular';
    }
    
    bootstrapApplication(App);
    
    
    

    Stackblitz Demo