2

I'm trying to call components with the string keys in angular 7. Because my service gives me keys which component filters about to show on page per user. Can i make this filter especially in html?

<pg-tab *ngFor="let tab of tabs; let index = index">
  <ng-template #TabHeading>
    {{tab.headerText}}
  <a (click)="removeTab(index)" style="padding: 5px;"><i class="fa fa-times fa-lg" style="color:orangered;"></i></a>
  </ng-template>
  <div class="row column-seperation" *ngIf="tab.componentName === 'grid-sample'">
    <app-grid-sample></app-grid-sample>
  </div>

  <div class="row column-seperation" *ngIf="tab.componentName === 'pdf-export-sample'">
    <app-pdf-export-sample></app-pdf-export-sample>
  </div>
  <div class="row column-seperation" *ngIf="tab.componentName === 'notify-sample'">
    <app-notify-sample></app-notify-sample>
  </div>
  <div class="row column-seperation" *ngIf="tab.componentName === 'loader-sample'">
    <app-loader-sample></app-loader-sample>
  </div>
  <div class="row column-seperation" *ngIf="tab.componentName === 'tt-form-elements'">
    <app-tt-form-elements></app-tt-form-elements>
  </div>
  <div class="row column-seperation" *ngIf="tab.componentName === 'tt-layouts'">
    <app-tt-layouts></app-tt-layouts>
  </div>
</pg-tab>

I searched about innerHtml attribute but it's not working for angular components like in that example.

html

<div [innerHTML]="pageDetail"></div>

typescript

private _pageDetail: string = '<app-tab-page1 [tab]="tab" [tabsLength]="tabs.length" [tabs]="tabs"></app-tab-page1><button>Naber</button>';

public get pageDetail(): SafeHtml {
  return this._sanitizer.bypassSecurityTrustHtml(this._pageDetail);
}

You can guess it's not looking good right now. If it's possible, I want to turn shorter and clean that "*ngIf" attributes in html.

Do you have any ideas about it?

Something like this

<div class="row column-seperation" tab.componentName>
  <app-tab.componentName></app-tab.componentName>
</div>

Note: Sorry for my grammar mistakes.

3 Answers 3

2

You can create a dynamic component with name like this:

Tab item is a directive:

@ViewChildren('tabItem', {read: ViewContainerRef} ) tabItem:  QueryList<ViewContainerRef>;

Here create dynamic component:

    const factories = Array.from(this.componentFactoryResolver['_factories'].keys());
    const factoryClass = <Type<any>>factories.find((x: any) => x.name === dynamicComponentName);
    if (factoryClass !== undefined) {
      const factory = this.componentFactoryResolver.resolveComponentFactory(factoryClass);
      const ref = this.tabItem.viewContainerRef;
      ref.createComponent(factory);
    }

Also you can look the angular docs: dynamic component

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

Comments

0

You could simply use ngSwitch which will make it shorter, but dynamic component name is from what i know not possible,

<div class="row column-seperation" *ngSwitch="tab.componentName">
    <app-grid-sample *ngSwitchCase="'grid-sample'"></app-grid-sample>
    <app-pdf-export-sample *ngSwitchCase="'pdf-export-sample''"></app-pdf-export-sample>
     //etc ...
  </div>

1 Comment

Actually that etc is a problem for me. it can be so long and i want to forget that place when i add new components.
0

There are two ways:

  1. Using ngSwitch will not help much to reduce the html code.
  2. Using ngComponentOutlet the component to display can be provided from parent component code instead of template.

Example for #2:

template:

<div>
... some other code
 <ng-template matTabContent>
     <ng-container *ngComponentOutlet="screen.component; injector: screen.injector">
     </ng-container>
 </ng-template>
</div>

Parent component code:

export class TabsContainerComponent implements OnInit {

  // make sure these component are also added to entryComponents
  private components = [
    GridSampleComponent,
    NotifySampleComponent,
    LoaderSampleComponent,
    TTLayoutComponent,
    ...
  ];

  screen: any; // reference to be used by the ngComponentOutlet in the template

  constructor(
    private injector: Injector,
  ) {}

  async ngOnInit() {
    const tabs = await this.someApiService.getComponentFilters().toPromise();
    const injector = Injector.create({
        providers: [any providers you want to pass to the component],
        parent: this.injector
    })

    switch (tabs.componentName) {
        case 'grid-sample':
            screen = { component: GridSampleComponent, injector };
            break;
        case 'notify-sample':
            screen = { component: NotifySampleComponent, injector };
            break;
        case 'loader-sample':
            screen = { component: LoaderSampleComponent, injector };
            break;
        case 'tt-layout':
            screen = { component: TTLayoutComponent, injector };
            break;
    }
  }
}

4 Comments

can we call that components with string? For example: screen = { component: "GridSampleComponent", injector };
You need to put your class in code anyways. I want to make it dynamically in anywhere. Like reflection in c#. stackoverflow.com/a/29034215/2351529
For the dependency injection to work, Angular needs to know the component at compile time. Hence the requirement to at-least add them in entryComponents array. Alternately, have a look at componentFactoryResolver api if you want to dynamically load a component.
Even for reflection, the class definition has to be part of the final/release bundle. In Angular entryComponents is an array to register component which are not directly or immediately used in the app, but instantiated at a later point in time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.