Necessary Pigeonholing Mechanism
    Have ideas to improve npm?Join in the discussion! »

    ng-dynamic-component
    TypeScript icon, indicating that this package has built-in type declarations

    8.0.1 • Public • Published

    ng-dynamic-component

    Dynamic components with full life-cycle support for inputs and outputs

    Travis CI Appveyor Coverage Maintainability Npm Npm Downloads Licence semantic-release Greenkeeper badge

    Angular ng-dynamic-component NPM package
    11.x.x 8.x.x ng-dynamic-component@^8.0.0
    10.x.x 7.x.x ng-dynamic-component@^7.0.0
    9.x.x 6.x.x ng-dynamic-component@^6.0.0
    8.x.x 5.x.x ng-dynamic-component@^5.0.0
    7.x.x 4.x.x ng-dynamic-component@^4.0.0
    6.x.x 3.x.x ng-dynamic-component@^3.0.0
    5.x.x 2.x.x ng-dynamic-component@^2.0.0
    4.x.x 1.x.x ng-dynamic-component@^1.0.0
    2.x.x 0.x.x ng-dynamic-component@^0.0.0

    Installation

    $ npm install ng-dynamic-component --save

    Usage

    DynamicComponent

    Import DynamicModule where you need to render dynamic components:

    import { DynamicModule } from 'ng-dynamic-component';
    
    @NgModule({
      imports: [DynamicModule],
    })
    export class MyModule {}

    Then in your component's template include <ndc-dynamic> where you want to render component and bind from your component class type of component to render:

    @Component({
      selector: 'my-component',
      template: `
        <ndc-dynamic [ndcDynamicComponent]="component"></ndc-dynamic>
      `,
    })
    class MyComponent {
      component = Math.random() > 0.5 ? MyDynamicComponent1 : MyDynamicComponent2;
    }

    NgComponentOutlet

    You can also use NgComponentOutlet directive from @angular/common instead of <ndc-dynamic>.

    Import DynamicIoModule where you need to render dynamic inputs:

    import { DynamicIoModule } from 'ng-dynamic-component';
    
    @NgModule({
      imports: [DynamicIoModule],
    })
    export class MyModule {}

    Now apply ndcDynamicInputs and ndcDynamicOutputs to ngComponentOutlet:

    @Component({
      selector: 'my-component',
      template: `<ng-template [ngComponentOutlet]="component"
                               [ndcDynamicInputs]="inputs"
                               [ndcDynamicOutputs]="outputs"
                               ></ng-template>`
    })
    class MyComponent {
      component = MyDynamicComponent1;
      inputs = {...};
      outputs = {...};
    }

    Also you can use ngComponentOutlet with * syntax:

    @Component({
      selector: 'my-component',
      template: `<ng-container *ngComponentOutlet="component;
                                ndcDynamicInputs: inputs;
                                ndcDynamicOutputs: outputs"
                                ></ng-container>`
    })
    class MyComponent {
      component = MyDynamicComponent1;
      inputs = {...};
      outputs = {...};
    }

    Inputs and Outputs

    You can pass inputs and outputs to your dynamic components:

    Import module DynamicIoModule and then in template:

    @Component({
      selector: 'my-component',
      template: `
        <ndc-dynamic
          [ndcDynamicComponent]="component"
          [ndcDynamicInputs]="inputs"
          [ndcDynamicOutputs]="outputs"
        ></ndc-dynamic>
      `,
    })
    class MyComponent {
      component = MyDynamicComponent1;
      inputs = {
        hello: 'world',
        something: () => 'can be really complex',
      };
      outputs = {
        onSomething: type => alert(type),
      };
    }
    
    @Component({
      selector: 'my-dynamic-component1',
      template: 'Dynamic Component 1',
    })
    class MyDynamicComponent1 {
      @Input()
      hello: string;
      @Input()
      something: Function;
      @Output()
      onSomething = new EventEmitter<string>();
    }

    Here you can update your inputs (ex. inputs.hello = 'WORLD') and they will trigger standard Angular's life-cycle hooks (of course you should consider which change detection strategy you are using).

    Output template variables

    Since v6.1.0

    When you want to provide some values to your output handlers from template - you can do so by supplying a special object to your output that has shape {handler: fn, args: []}:

    @Component({
      selector: 'my-component',
      template: `
        <ndc-dynamic
          [ndcDynamicComponent]="component"
          [ndcDynamicOutputs]="{
            onSomething: { handler: doSomething, args: ['$event', tplVar] }
          }"
        ></ndc-dynamic>
      `,
    })
    class MyComponent {
      component = MyDynamicComponent1;
      tplVar = 'some value';
      doSomething(event, tplValue) {}
    }

    Here you can specify at which argument event value should arrive via '$event' literal.

    HINT: You can override event literal by providing EventArgumentToken in DI.

    Component Creation Events

    You can subscribe to component creation events, being passed a reference to the ComponentRef:

    @Component({
      selector: 'my-component',
      template: `
        <ndc-dynamic
          [ndcDynamicComponent]="component"
          (ndcDynamicCreated)="componentCreated($event)"
        ></ndc-dynamic>
      `,
    })
    class MyComponent {
      component = MyDynamicComponent1;
      componentCreated(compRef: ComponentRef<any>) {
        // utilize compRef in some way ...
      }
    }

    Attributes

    Since v2.2.0 you can now declaratively set attributes, as you would inputs, via ndcDynamicAttributes.

    Import module DynamicAttributesModule and then in template:

    import { AttributesMap } from 'ng-dynamic-component';
    
    @Component({
      selector: 'my-component',
      template: `
        <ndc-dynamic
          [ndcDynamicComponent]="component"
          [ndcDynamicAttributes]="attrs"
        ></ndc-dynamic>
      `,
    })
    class MyComponent {
      component = MyDynamicComponent1;
      attrs: AttributesMap = {
        'my-attribute': 'attribute-value',
        class: 'some classes',
      };
    }

    Remember that attributes values are always strings (while inputs can be any value). So to have better type safety you can use AttributesMap interface for your attributes maps.

    Also you can use ngComponentOutlet and ndcDynamicAttributes with * syntax:

    import { AttributesMap } from 'ng-dynamic-component';
    
    @Component({
      selector: 'my-component',
      template: `
        <ng-container
          *ngComponentOutlet="component; ndcDynamicAttributes: attrs"
        ></ng-container>
      `,
    })
    class MyComponent {
      component = MyDynamicComponent1;
      attrs: AttributesMap = {
        'my-attribute': 'attribute-value',
        class: 'some classes',
      };
    }

    Directives (experimental)

    Since v3.1.0 you can now declaratively set directives, via ndcDynamicDirectives.

    NOTE: In dynamic directives queries like @ContentChild and host decorators like @HostBinding will not work due to involved complexity required to handle it (but PRs are welcome!).

    Import module DynamicDirectivesModule and then in template:

    import { dynamicDirectiveDef } from 'ng-dynamic-component';
    
    @Component({
      selector: 'my-component',
      template: `
        <ng-container
          [ngComponentOutlet]="component"
          [ndcDynamicDirectives]="dirs"
        ></ng-container>
      `,
    })
    class MyComponent {
      component = MyDynamicComponent1;
      dirs = [dynamicDirectiveDef(MyDirective)];
    }

    It's also possible to bind inputs and outputs to every dynamic directive:

    import { dynamicDirectiveDef } from 'ng-dynamic-component';
    
    @Component({
      selector: 'my-component',
      template: `
        <ng-container
          [ngComponentOutlet]="component"
          [ndcDynamicDirectives]="dirs"
        ></ng-container>
      `,
    })
    class MyComponent {
      component = MyDynamicComponent1;
      directiveInputs = { prop1: 'value' };
      directiveOutputs = { output1: evt => this.doSomeStuff(evt) };
      dirs = [
        dynamicDirectiveDef(
          MyDirective,
          this.directiveInputs,
          this.directiveOutputs,
        ),
      ];
    }

    To change inputs, just update the object:

    class MyComponent {
      updateDirectiveInput() {
        this.directiveInputs.prop1 = 'new value';
      }
    }

    You can have multiple directives applied to same dynamic component (only one directive by same type):

    import { dynamicDirectiveDef } from 'ng-dynamic-component';
    
    @Component({
      selector: 'my-component',
      template: `
        <ng-container
          [ngComponentOutlet]="component"
          [ndcDynamicDirectives]="dirs"
        ></ng-container>
      `,
    })
    class MyComponent {
      component = MyDynamicComponent1;
      dirs = [
        dynamicDirectiveDef(MyDirective1),
        dynamicDirectiveDef(MyDirective2),
        dynamicDirectiveDef(MyDirective3),
        dynamicDirectiveDef(MyDirective1), // This will be ignored because MyDirective1 already applied above
      ];
    }

    Extra

    You can have more advanced stuff over your dynamically rendered components like setting custom injector ([ndcDynamicInjector]) or providing additional/overriding providers ([ndcDynamicProviders]) or both simultaneously or projecting nodes ([ndcDynamicContent]).

    NOTE: In practice functionality of this library is split in two pieces:

    • one - component (ndc-dynamic) that is responsible for instantiating and rendering of dynamic components;
    • two - directive (ndcDynamic also bound to ndc-dynamic) that is responsible for carrying inputs/outputs to/from dynamic component by the help of so called DynamicComponentInjector.

    Thanks to this separation you are able to connect inputs/outputs and life-cycle hooks to different mechanisms of injecting dynamic components by implementing DynamicComponentInjector and providing it via DynamicComponentInjectorToken in DI.

    It was done to be able to reuse NgComponentOutlet added in Angular 4-beta.3.

    To see example of how to implement custom component injector - see ComponentOutletInjectorDirective that is used to integrate NgComponentOutlet directive with inputs/outputs.

    License

    MIT © Alex Malkevich

    Install

    npm i ng-dynamic-component

    DownloadsWeekly Downloads

    21,276

    Version

    8.0.1

    License

    MIT

    Unpacked Size

    529 kB

    Total Files

    64

    Last publish

    Collaborators

    • avatar