0
votes

I’m trying to enhance an angular table component I created to output formatted cell content instead of just text. I thought a set of format components would be the way to go as they will have to contain html tags (which would be stripped by simply binding to a property). I guess pipes might have been an option but as most of the functionality is in a base component I don't want much in the way of specific code in instances of the table.

So I tried the Angular document on Dynamic Component Loading and created a [format-host] directive, linked it all up as in the doc & tried to debug. The failure is in the component that uses the directive.

let viewContainerRef = this.formatHost.viewContainerRef; 

this fails as formatHost is undefined. The directive is defined in the module.

I'm using the VS2017 angular SPA template, angular 4.2.5 The relevant code in the using component is (kept it short):

export class PagedTableComponent implements OnInit, AfterViewInit{

@Input() pagedTable: PagedTable<any[]>;
@Output() tableState: EventEmitter<TableState> = new EventEmitter<TableState>();
…
@ViewChild(FormatDirective) formatHost: FormatDirective;
…
formatters: Formatter[] = [];

constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}

loadFormatComponent() {
    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.formatters[0].component);

    let viewContainerRef = this.formatHost.viewContainerRef;
    viewContainerRef.clear(); //??
    let componentRef = viewContainerRef.createComponent(componentFactory);
}

ngOnInit() {
  …
    //this.formatters.push(new Formatter(ContactFormatComponent, "test"));
    // this.loadFormatComponent();
}

ngAfterViewInit() {
    this.formatters.push(new Formatter(ContactFormatComponent, "test"));
    this.loadFormatComponent();
}

Any idea on why the directive is undefined would be appreciated.

directive:

import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
    selector: '[format-host]',
    })

export class FormatDirective {
    constructor(public viewContainerRef: ViewContainerRef) { }
}

template:

<td *ngFor="let column of pagedTable.tableConfig.columns"
     [ngClass]="{ 'cell-right' : column.justification ===  ColumnJustification.right, 'cell-center' : column.justification === ColumnJustification.center}">
....
<ng-template format-host></ng-template>
</td>
1
Can you provide your component template? - Daniel W Strimpel
added template above - Bod
Am I correct in that you will have multiple format-host items? It seems like you need to use @ViewChildren instead of @ViewChild since you will have many to process - Daniel W Strimpel
thanks Daniel I'll take a look at ViewChildren - Bod

1 Answers

0
votes

Finally got something working - (pasted below if it helps anyone) thanks for the prod to ViewChildren. I can now get a test Format componet to appear with content - but only when I step through the debugger in Chrome. When just in a browser the column is empty. I'm thinking a timing issue (tried setTimeout) but can't fathom the intricasies of Angular.

The template now looks like :

<tr *ngFor="let row of pagedTable.pagedList?.tableData">
    <td *ngFor="let column of pagedTable.tableConfig.columns">
        <div *ngIf="column.format != undefined; else normalCell">                            
            <ng-template avFormatHost rowId="{{row.id}}" formatName="{{column.format}}"></ng-template>
        </div>
        <ng-template #normalCell>{{getCellContent(row, column)}}</ng-template>
    </td>
</tr>

and relevant component code:

 @ViewChildren(FormatHostDirective) formatHostDirectives: QueryList<FormatHostDirective>;

...

loadFormatComponent() {
    setTimeout(() => {
        this.formatHostDirectives.forEach((formatHostDirective) => {

            let rowId = formatHostDirective.rowId;
            let row = { email: 'test email', telephone: '999999' };

            let componentType: any = FormatComponents[formatHostDirective.formatName];
            if (componentType) {
                let componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);

                //formatHostDirective.viewContainerRef.clear(); //??
                let componentRef = formatHostDirective.viewContainerRef.createComponent(componentFactory);
                this.components.push(componentRef);
                (<FormatComponent>componentRef.instance).data = row;
            }

        });
    });


}

Is the use of timeout correct? or should it be elsewhere? or something else entirely. How do you decide what a safe delay is?