0

I'd like to render custom component inside mat-option. This is the code in my parent component:

  <mat-option *ngFor="let option of options" [value]="option.value">
   HERE I WOULD LIKE TO INJECT SOME COMPONENT
  </mat-option>

Let's assume that I have a component called CustomOptionView:

@Component({
    selector: 'custom-option-view',
    template: `<h1> {{ label }} </h1>`,
})
export class CustomOptionView{
@Input() label?: string;
}

And I would like to create that component manually in my child component like this:

const factory = this.componentFactoryResolver.resolveComponentFactory(CustomOptionView);

How Can I pass this instance to parent component and render it?

Why I am trying to do that? Because I would like to have options in selectlist that looks differently. One component needs to show icons and multiline text and other doesn't need it. I came to conclusion that I need a set of custom components that I could somehow inject to parent's html.

tylkonachwile
  • 2,025
  • 4
  • 16
  • 28
  • Why don't you write the custom tag directly inside mat-option? `` – Liero Mar 03 '23 at 08:09
  • As I wrote I do not want to hardcode it. I'd like to have opprotunity to inject any component there. Today it is CustomOptionView, but tommorow I may have other component. – tylkonachwile Mar 03 '23 at 08:12
  • @tylkonachwile: I have written an answer on how to solve this, but I think for your use case it is overkill. I would just write plain simple `*ngIf..else` and if you need to reuse the logic, simply wrap that in a separate component. – Liero Mar 03 '23 at 11:58

2 Answers2

1

general API for dynamically creating a component

You can dynamically add a component at runtime using viewContainerRef.createComponent() API

See guide: https://angular.io/guide/dynamic-component-loader

ready to use solution


The above mentioned mechanism is already implemented in built in *ngComponentOutlet directive.

See docs: https://angular.io/api/common/NgComponentOutlet

basically, ngComponentOutlet takes the component type as a parameter.

@Component({
  selector: 'my-app',
  template: `<ng-container *ngComponentOutlet="dynamicComponentToLoad"></ng-container>
             <button (click)="switchComponent()">Switch components</button>`
})
export class AppComponent {
  // This field is necessary to expose HelloWorld to the template.
  dynamicComponentToLoad: Type<any> = Component1;

  switchComponent() {
     dynamicComponentToLoad = Component2;
  }
}

In you case it could look like this:

<mat-option *ngFor="let option of options" [value]="option.value">
   <ng-container *ngComponentOutlet="option.componentType; injector:createInjector(option)"></ng-container>
</mat-option>
Liero
  • 25,216
  • 29
  • 151
  • 297
0

you could use NgComponentOutletDirective to do that. example

 <mat-option *ngFor="let option of options" [value]="option.value">
     <ng-container *ngIf="option.type=='type1'">
        <ng-container *ngComponentOutlet="component1">
     </ng-container>

  </mat-option>
Andrei
  • 10,117
  • 13
  • 21
  • how should I pass a component generated in child to parent to make it work? – tylkonachwile Mar 03 '23 at 08:13
  • you could render it in the cild's template if you want to. if my example if not the thing that you need then could you please expand the phrase "inject to parent's html.". what do you mean by that? – Andrei Mar 03 '23 at 08:16
  • I didn't understand your solution initialy but now it is clear. Thanks. Do you have an idea how to avoid *ngIf and make it more generic? It would be great to avoid modyfing that code in the feature. – tylkonachwile Mar 03 '23 at 08:31
  • you could use ngSwitch + ngSwitchCase, or even put this whole logic into a separate component – Andrei Mar 03 '23 at 08:35
  • making a getter in typescript is possible as well `getOptionComponent(type: string) {return typeToClassMap[type];}` – Andrei Mar 03 '23 at 08:38