简体   繁体   中英

Creating multiple Angular components dynamically

I have an array of data objects:

const arr = [
   {type: 'CustomA', id: 1, label: 'foo'},
   {type: 'CustomB', src: './images/some.jpg'}
   {type: 'CustomX', firstName: 'Bob', secondName:'Smith'}
]

These objects will always have a type but the rest of the properties on each will be specific to that type.
I have a component set up that can accept and display each of the possible data objects, doing whatever parsing and internal logic is required.
I want to look through that list, check the type of each object that I find and add the relevant component to the page. So, if I find type: 'CustomA I can add a CustomADisplay component to the page and pass it the data object to work with.

How best to achieve this when I don't know in advance how many items will be in that list or what their types will be?
Basically, who can I dynamically create and add components to page while the app is running.
I am currently playing around with ViewContainerRef as per the docs but this seems to require a target element in the template and until I get my data I'm not going to know how many of these are needed.

Hope that makes sense. Any advice very gratefully received.

I guess that you are thinking off in something like this:

HTML for your array

arr = [
      {type: 'CustomA', id: 1, label: 'foo'},
      {type: 'CustomB', src: './images/some.jpg'}
      {type: 'CustomX', firstName: 'Bob', secondName:'Smith'}
];

 <ng-container *ngFor="let item of arr" [ngSwitch]="item.type">

    <ng-container *ngSwitchCase="CustomA">
       <app-custom-a-display [data]="item"><app-custom-a-display>
    </ng-container>

    <ng-container *ngSwitchCase="CustomB">
       <app-custom-b-display [data]="item"><app-custom-b-display>
    </ng-container>

    <ng-container *ngSwitchDefault>
       <app-custom-x-display [data]="item"><app-custom-x-display>
    </ng-container>

  </ng-container>

TS example for your CustomADisplay:

...
@Component({
  selector: 'app-custom-a-display',
  templateUrl: './custom-a-display.component.html',
  styleUrls: ['./custom-a-display.component.scss'],
})
export class CustomADisplayComponent {
private _localData;
@Input()
get data() {
   return _localData;
}
set data(data) {
    this._localData= data;   
}
arr:any[] =[];

constructor() {
// you will have your {type: 'CustomA', id: 1, label: 'foo'}
// accesible in 'data' varaible
}

   
}

The above answer gets the job done but just like with any other switch case you will need to keep on adding more switch conditions for each type. Like talha mentioned in the components it might be better to use dynamic component rendering. Although this solution requires more configuration but it should be easier to manage in the long run. Essentially something like this. heres the working stackblitz link .

const datas = [
   {type: 'CustomA', id: 1, label: 'foo'},
   {type: 'CustomB', src: './images/some.jpg'}
   {type: 'CustomX', firstName: 'Bob', secondName:'Smith'}
]
import AComponent from 'wherever';
import BComponent from 'wherever';
import XComponent from 'wherever';
// constants file
export const TypeMappedComponents = {
  CustomA: AComponent,
  CustomB: BComponent,
  CustomX: XComponent
}
// dynamic-renderer.component.html
<ng-template appRenderDynamically></ng-template>
//dynamic-renderer.component.ts
import { TypeMappedComponents } from '../constants';
import { RenderDynamicallyDirective } from '../render-dynamically.directive';

@Component(...all the decorator stuff)
export class DynamicRendererComponent {
  _data: Record<string, unknown> = {};
  componentType = '';
  @Input() set data(data: Record<string, unknown>) {
    this._data = data;
    this.componentType = data.type as string;
    this.componentType && this.createDynamicComponent(this.componentType);
  }
  @ViewChild(RenderDynamicallyDirective, { static: true })
  renderDynamic: RenderDynamicallyDirective;

  constructor(private cfr: ComponentFactoryResolver) {}

  createDynamicComponent(type: string) {
    const component = this.cfr.resolveComponentFactory(
      TypeMappedComponents[type]
    );
    this.renderDynamic.vcr.clear();
    const componentRef = this.renderDynamic.vcr.createComponent(
      component
    ) as any;
    componentRef.instance.data = this._data;
  }
}

and then you just loop it and pass the data to dynamic-renderer. This should dynamically create the component according to the type.

<ng-container *ngFor="let data of datas">
  <app-dynamic-renderer [data]="data"></app-dynamic-renderer>
</ng-container>

Heres the renderDynamicallyDirective which places the component into the view.

@Directive({
  selector: '[appRenderDynamically]'
})
export class RenderDynamicallyDirective {
  constructor(public vcr: ViewContainerRef) { }
}

Although this code is longer than the switch case, its more of a one time effort. Since if you have a new type and new component to go along with it you just need to created the required component. then add the new type and the component associated with it to the TypeMappedComponent .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM