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.

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

1 Comment

If the controls are dynamic and can repeat, how would I dynamically create a TemplateRef for each item? (There could be 10 AControl(s))
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().

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.