angulartypescriptdirectiveng-templateangular-content-projection

Why does ContentChilden never output template references?


I want to make a modular reusable table. Trying to use directives to generate the columns, so i can use templates for any custom cell content. I have been trying for a couple of days to reach my TemplateRefs, but it always comes out empty.

Angular is v10, as the project is frozen in time as businesses do. My mind simply cannot handle understanding what issues might be, it's too convoluted, too many things to check and i see there's long lasting issues around this, perfect recipe for driving me nuts.

What's wrong and what can i do to accomplish my goal?

= the component containing the table =

[HTML]

<app-table2 #ordTable [rows]="rows" [columns]="columns">
    <ng-template column columnId="order_id" let-row>
            {{row.order_id}}
    </ng-template>
</app-table2>

[TS]

columns: Table2Column[] = [
    {identifier: "order_id", title: "ORDER #"},
    ...
];

= table component =

[HTML]

<div>table 2</div>

<div class="table-row-{{i}}" *ngFor="let row of rows; let i = index">
  <div class="table-cell checkbox-cell">
  </div>
  <ng-container *ngFor="let column of columns">
    <div class="table-cell {{ column.extraClass }}">
      <ng-container *ngTemplateOutlet="dict[column.identifier]; context:{$implicit: row}"></ng-container>
    </div>
  </ng-container>
</div>

[TS]

export interface Table2Column {
  identifier: string;
  title: string;
  sizing?: string;
  extraClass?: string;
}


@Directive({
  selector: "[column]"
})
export class ColumnTemplateDirective {
  @Input('columnId') column!: string;

  constructor(public templateRef: TemplateRef<any>) {}
}

@Component({
    selector: 'app-table2',
    templateUrl: './table2.component.html',
    styleUrls: ['./table2.component.scss']
})
export class Table2Component implements OnInit {
  @ContentChildren(ColumnTemplateDirective) columnTemplates!: QueryList<ColumnTemplateDirective>;

  dict: { [key: string]: TemplateRef<any> } = {};

  @Input() rows: Array<any>;
  @Input() columns: Array<Table2Column>;

  constructor() {}

  ngOnInit(): void {}

  ngAfterContentInit() {
    console.log('asdasdasdsad', this.columnTemplates);
    this.columnTemplates.forEach(x => {
      this.dict[x.column] = x.templateRef
    });
    console.log('dict', this.dict);
  }
}

Whatever I do I'm never getting template references, and we have a working version of this technique on another part of the project, where we run Angular 12.

Have i ran into THE issue of this version perhaps?


Solution

  • The ContentChildren, has a second argument with a property read where you can supply, which directive/component to access. You can use this and directly access the templateRef.

    Metadata Properties (from the Docs):

    ...
    read - Used to read a different token from the queried elements.

    ...
    @ContentChildren(TemplateRef, { read: TemplateRef }) columnTemplates!: QueryList<TemplateRef<any>>;
    ...
    

    OR

    ...
    @ContentChildren('column', { read: TemplateRef }) columnTemplates!: QueryList<TemplateRef<any>>;
    ...
    

    Then in the HTML:

    <app-table2 #ordTable [rows]="rows" [columns]="columns">
        <ng-template #column column columnId="order_id" let-row>
                {{row.order_id}}
        </ng-template>
    </app-table2>