Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different ChildComponent based on data

How can I change an Component based on data that I get from database?
I'm using something like this: stackblitz
But tools are show depending on user roles. (Database returns which tools, order and so on)

There are two ways I can think of how to do this:

First
Using *ngIf like:

<div class="tools-wrapper controls">
  <div class="tools-container">
    <app-tool-menu *ngIf="existsInData('menu')"></app-tool-menu>
    <mat-divider *ngIf="existsInData('menu')"></mat-divider>
    <app-tool-refresh *ngIf="existsInData('refresh')"></app-tool-refresh>
    <app-tool-info *ngIf="existsInData('info')"></app-tool-info>
    <app-tool-edit *ngIf="existsInData('edit')"></app-tool-edit>
  </div>
</div>

this is not dynamically enough.

Second Using the factoryResolver

@Injectable()
export class InjectorService {
  constructor(private factoryResolver: ComponentFactoryResolver) { }

  setRootViewContainerRef(viewContainerRef) {
    this.rootViewContainer = viewContainerRef;
  }

  addDynamicComponents(data: any[]) {
    const component = factory.create(this.rootViewContainer.parentInjector);

    data.forEach((tool) => {
      const toolComponent = this.toolMapping(tool);
      const factory = this.factoryResolver.resolveComponentFactory(toolComponent);
      this.rootViewContainer.insert(component.hostView);
    });
  }

  toolMapping(tool) {
    const tools = {
      'menu': MenuComponent,
      'refresh': RefreshComponent,
      'info': InfoComponent,
      'edit': EditComponent,
    };
    return tools[tool];
  }
}



@Component({
  selector: 'my-app',
  template: `
    <h1>Hello {{name}}</h1> 
    <ng-template #dynamic></ng-template>
  `
})
export class AppComponent implements OnInit {
  name = 'from Angular';

  @ViewChild('dynamic', { read: ViewContainerRef }) 
  viewContainerRef: ViewContainerRef;

  constructor(private service: InjectorService ) {}

  ngOnInit() {
    this.service.setRootViewContainerRef(this.viewContainerRef);
    this.service.addDynamicComponents(.. data ..);
  }
}

Well there is a lot of overhead in this code, like defining a toolMapping or creating a Service for each similar construct I have.

Is there a better and cleaner solution?

Data looks like this (Can be modified if necessary):

[
  {type: 'menu', tooltip: '', ...},
  {type: 'divider', tooltip: '',...},
  {type: 'refresh', tooltip: '', ...},
  {type: 'info', tooltip: '',...},
  {type: 'edit', tooltip: '',...},
]
like image 573
domiSchenk Avatar asked Feb 12 '26 14:02

domiSchenk


1 Answers

How about using the <ng-container> with ngComponentOutlet? It's much less verbose than the ComponentFactoryResolver solution.

import { Component } from '@angular/core';

import { AComponent } from './a.component';
import { BComponent } from './b.component';
import { CComponent } from './c.component';

@Component({
  selector: 'my-app',
  template: `
    <input type="radio" value="A" name="comp" [(ngModel)]="radioValue"> A
    <input type="radio" value="B" name="comp" [(ngModel)]="radioValue"> B
    <input type="radio" value="C" name="comp" [(ngModel)]="radioValue"> C

    <ng-container [ngComponentOutlet]="selected"></ng-container>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  radioValue: 'A' | 'B' | 'C';

  get selected() {
    switch (this.radioValue) {
      case 'A': {
        return AComponent
      }
      case 'B': {
        return BComponent
      }
      case 'C': {
        return CComponent
      }
    }
  }
}

Live demo

like image 193
Tomasz Kula Avatar answered Feb 15 '26 10:02

Tomasz Kula