/angular-dynamic-components

Created with StackBlitz ⚡️

Primary LanguageTypeScriptMIT LicenseMIT

NGX Dynamic

Best way to quickly use Dynamic Components with Angular

Npm Npm Downloads Npm Downloads Weekly Travis Licence

GitHub Contributors GitHub PR GitHub Issue GitHub Stars

NGX Dynamic v0.x to v7 Update Guide

NGX Dynamic v7 will arriving soon! While this is a major version change (from 0.x to 7.x).

Example Usage

Use like NgComponentOutlet but with @Input/@Output auto bindings:

<ng-container *ngxComponentOutlet="component"></ng-container>

Dynamic Components In 5 minutes

There are several modes of operation of the directive.

Through The Parent Component

A simple variant of binding through the parent component.

@Component({
  template: `
    <ng-container
      *ngxComponentOutlet="component"
    ></ng-container>
  `
})
class DynamicComponent {
  @Input() entity;
  @Output() action;
}

Through The Context

Additionally there is autobinding through the context. This is useful when you need to display something through *ngFor. Context has a higher priority than the inputs in the component.

<ng-container *ngFor=“let color of colors”
  <ng-container
    *ngxComponentOutlet="
      component;
      context: { color: color }
  "></ng-container>
</ng-container>

Pipe For Selecting The Component

For ease of selecting the required component, there is ResolvePipe, which expects NgxdResolver to enter, and returns the required component.

<ng-container
  *ngxComponentOutlet="
    resolver | resolve: entity
"></ng-container>

Through The Host Component (deprecated)

Through the host component, when the inputs and outputs are initialized explicitly. This option is difficult to use and deprecated.

<!-- host component -->
<app-dynamic
    <!-- dynamic component -->
    [ngxComponentOutlet]="component"
    <!-- regular input -->
    [entity]="entity"
    <!-- regular output -->
    (action)="onAction($event)">
</app-dynamic>

Comparison

Feature NgxComponentOutlet ComponentFactoryResolver NgComponentOutlet
Friendliness ⭐⭐⭐ ⭐⭐
Dynamic Components
AOT support
Reactivity
Injector
NgModule
projectionNodes
Component Access
Lifecycle OnChanges ⭕️ manually
Binding @Input() ⭕️ manually
Binding @Output() ⭕️ manually
Activate Event ⭕️ manually
Deactivate Event ⭕️ manually

Demo

List of heroes

Table of heroes with table schema form

Editable Demo

Stackblitz

Github

Getting started

Step 1: Install @ngxd/core:

NPM

npm install --save @ngxd/core

Yarn

yarn add @ngxd/core

Step 2: Import the NgxdModule:

import { NgxdModule } from '@ngxd/core';

@NgModule({
  declarations: [ AppComponent ],
  imports: [ BrowserModule, NgxdModule ],
  bootstrap: [ AppComponent ]
})
export class AppModule {}

Step 3: Create components that your want to use dynamically:

@Component({
    selector: 'app-dynamic-comp-a',
    template: `I'm Dynamic Component A. Hello, {{ name }}!`
})
export class CompAComponent {
    @HostBinding('style.color') @Input() color: string;
    @Input() name: string;
    @Output() action: EventEmitter<any> = new EventEmitter<any>();
    
    @HostListener('click', ['$event.target'])
    onClick($event) {
        this.action.emit($event);
    }
}
@Component({
    selector: 'app-dynamic-comp-b',
    template: `I'm Dynamic Component B. Hello, {{ name }}!`
})
export class CompBComponent {
    @HostBinding('style.color') @Input() color: string;
    @Input() name: string;
    @Output() action: EventEmitter<any> = new EventEmitter<any>();
    
    @HostListener('click', ['$event.target'])
    onClick($event) {
        this.action.emit($event);
    }
}

Step 4: Add components to declarations and entryComponents:

@NgModule({
    ...
    declarations: [ ..., CompAComponent, CompBComponent ],
    entryComponents: [ CompAComponent, CompBComponent ]
})
export class AppModule {}

Step 5: Create a host component with the same inputs/outputs:

@Component({
    selector: 'app-items',
    template: `
    <ng-container *ngFor="let item of items">
        <ng-container *ngxComponentOutlet="
            item.component;
            context: { name: item.name }
        "></ng-container>
    </ng-container>
    `
})
export class ItemsComponent {
    @Input() color: string;
    @Input() items: { name: string; component: Type<any> }[];
    @Output() action: EventEmitter<any> = new EventEmitter<any>();
}

Step 6: Add the component to declarations:

@NgModule({
    ...
    declarations: [ ..., ItemsComponent ],
    ...
})
export class AppModule {}

Step 7: Now show dynamic component in AppComponent:

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

@Component({
  selector: 'app-root',
  template: `
    <app-items
      [items]="items"
      (action)="onAction($event)"
      color="red"
    ></app-items>
  `
})
export class AppComponent {
  items = [
    {
      name: 'Angular 5!',
      component: CompAComponent
    },
    {
      name: 'Angular 6!',
      component: CompBComponent
    }
  ];
    
  onAction($event) {
    console.log($event);
  }
}

And you will have in AppModule:

import { NgxdModule } from '@ngxd/core';

@NgModule({
  imports:      [ BrowserModule, NgxdModule ],
  declarations: [ AppComponent, CompAComponent, CompBComponent, ItemsComponent ],
  entryComponents: [ CompAComponent, CompBComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule {}

API

Input Type Default Required Description
[ngxComponentOutlet] Type<any> n/a yes
[ngxComponentOutletContext] any n/a no
[ngxComponentOutletInjector] Injector n/a no
[ngxComponentOutletContent] any[][] n/a no
[ngxComponentOutletNgModuleFactory] NgModuleFactory<any> n/a no
Output Type Description
(ngxComponentOutletActivate) any
(ngxComponentOutletDeactivate) any

Advanced Use Cases

Here is a demo repository showing NGX Dynamic and Angular in action.