Search code examples
angularrxjsangular-forms

Angular Row not displaying using Ng-for to display NgTemplate


I'm using an ngFor in a container to display template rows corresponding to the type, instead of hardcoding the whole form. However I can't get any rows to display. I've tried making an ngif and forcing everything to display.

HTML

 <ng-container
    *ngFor="let column of filteredColumns"
    [ngTemplateOutlet]="getColumnInputType(column)"
    [ngTemplateOutletContext]="{ $implicit: column }"
  ></ng-container>
</ng-container>
<ng-container *ngIf="displayAsRow">
  <ng-container *ngFor="let column of filteredColumns">
    <c-form-row [title]="column.title" [description]="column.description">
      <ng-container [ngTemplateOutlet]="getColumnInputType(column)" [ngTemplateOutletContext]="{ $implicit: column }"></ng-container>
    </c-form-row>
  </ng-container>
</ng-container>

HTML example of ng-template by type

    <ng-template #text let-column>
  <div *cHasRole="checkPermissions(column.permissions)" class="d-flex align-items-center justify-content col-3 mb-3">
    <mat-form-field [ngClass]="{ 'col-11': column?.description }" appearance="outline" stateChange="onFieldChange(column)">
      <mat-label>{{ column.title | translate }} {{ column.validation && column.validation.required ? ' *' : '' }}</mat-label>
      <input matInput [formControlName]="column.field" [readonly]="checkColumnReadOnly(column)" />
      <mat-hint *ngIf="column.hintLabel">{{ column.hintLabel }}</mat-hint>
      <mat-error *ngIf="checkValidationErrors(column, 'required')">
        {{ util.formatFormErrorMsg(column.title, 'required') }}
      </mat-error>
      <mat-error *ngIf="checkValidationErrors(column, 'pattern')">
        {{ util.formatFormErrorMsg(column.title, 'pattern') }}
      </mat-error>
    </mat-form-field>
    <h5 *ngIf="column?.description" class="cursor-pointer col-1 pl-0" [ngbTooltip]="column.description" placement="right-center">
      <i class="far fa-info-circle text-info"></i>
    </h5>
  </div>
</ng-template>

Method that is returning the string:

getColumnInputType(column: any): string {
  if (column.type.toLowerCase() === 'datetime' || column.type.toLowerCase() === 'date' || column.type.toLowerCase() == 'boolean') {
    return column.type.toLowerCase();
  }
  return column.inputMetaData.inputType.toLowerCase();
}

Error  in console


Solution

  • The problem is that getColumnInputType returns a string but the ng-container ngTemplateOutlet input expects a TemplateRef.

    There are 2 ways to fix this:

    1. Either send the template reference types to the function that is checking the type:

    getColumnInputType(column: any, textTemplate: TemplateRef): TemplateRef {
      if (column.type === 'text') {
        return textTemplate;
      }
    
      //TODO: what if you don't have a template for the given type
    }
    

    so in your HTML you can do something like:

    <ng-container
      *ngFor="let column of filteredColumns"
      [ngTemplateOutlet]="getColumnInputType(column, text)"
      [ngTemplateOutletContext]="{ $implicit: column }"
    >
    </ng-container>
    
    <ng-template #text let-column>
      ...
    </ng-template>
    

    2. Capture the references by their name in the component:

    @ViewChild('text') textTemplate: TemplateRef;
    
    getColumnInputType(column: any): TemplateRef {
      if (column.type === 'text') {
        return this.textTemplate;
      }
    
      //TODO: what if you don't have a template for the given type
    }
    

    and in the HTML:

    <ng-container
      *ngFor="let column of filteredColumns"
      [ngTemplateOutlet]="getColumnInputType(column)"
      [ngTemplateOutletContext]="{ $implicit: column }"
    >
    </ng-container>
    
    <ng-template #text let-column>
      ...
    </ng-template>