2

I really like the answer provided for Dynamic template based on value rather than variable with ngTemplateOutlet. However I'm not able to get it to work. Simplified example:

export class A {
}

export class MyComponent 
  implements OnInit {

  public controls$ = Observable<any[]>([]);

  ngOnInit() {
    this.controls$.next([new A()]);
  }

  public getTypeName(control: any) {
    if (control instanceof A) {
      return "AControl";
    }
    return "";
  }
}

Template:

<div *ngFor="let control of control$ | async">
  {{ getControlType(control) }}
</div>

Yields:

AControl

So far so good. When I add a template, I get an exception:

<div *ngFor="let control of control$ | async">
  {{ getControlType(control) }}
  <ng-container 
    [ngTemplateOutlet]="getControlType(control)"
    [ngTemplateOutletContext]="{ control: control }">
  </ng-container>
</div>

<ng-template 
  #AControl 
  let-item="control">A Control</ng-template>

throws:

templateRef.createEmbeddedView is not a function

I'm not sure what I need to change in order for the template #AControl to render in the container.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Erik Philips
  • 53,428
  • 11
  • 128
  • 150

2 Answers2

1

seems like [ngTemplateOutlet]="getControlType(control)" is the culprit. I don't see the code for getControlType(), but I suppose it's returning a s string, while it should be a TemplateRef object. calling functions in template is not a good idea unless you use ChangeDetectionStrategy.OnPush, so I suggest using a switch statement in your template instead. Nevertheless, with

@ViewChild("AControl", {static: true})
AControl: TemplateRef<any>;

you can access the template in your .ts file and return in from getControlType function

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
D Pro
  • 1,756
  • 1
  • 8
  • 15
  • If the controls are dynamic and can repeat, how would I dynamically create a `TemplateRef` for each item? (There could be 10 AControl(s)) – Erik Philips Oct 28 '19 at 07:09
1

With the help of D Pro's Answer I discovered, I really didn't need to use a ngTemplateOutlet in my instance (they seem to be really complicated for something as simple as I was using).

My final solution was:

typescript

export class A {
}

export class B {
}


export class MyComponent 
  implements OnInit {

  public controlsTypes$ = Observable<any[]>([]);

  ngOnInit() {
    var value1 = new A();
    var value2 = new B();
    this.controls$.next([
      { control: value1, type: getTypeName(value1)},
      { control: value2, type: getTypeName(value2)},
    ]);
  }

  public getTypeName(control: any) {
    if (control instanceof A) {
      return "AControl";
    } else if (control instanceof B) {
      return "BControl";
    }
    return "";
  }

  public onClick(control: any) {
  }
}

html:

<div *ngFor="let controlType of controlType$ | async"
     [ngSwitch]="controlType.type">
  <ng-template ngSwitchCase="AControl">
    <div (click)="onClick(controlType.control)">{{ controlType.type }}</div>
  </ng-template>
  <ng-template ngSwitchCase="BControl">
    <div (click)="onClick(controlType.control)">{{ controlType.type }}</div>
  </ng-template>
</div>

Shows both an AControl and BControl and when each are clicked the instantiated class is passed to onClick().

Erik Philips
  • 53,428
  • 11
  • 128
  • 150