Dynamic components with full life-cycle support for inputs and outputs for Angular
Branch: master
Clone or download
gund Merge pull request #211 from gund/fix-directive-hooks
fix(directives): fire ngDoCheck hook for dynamic directives
Latest commit f8c3348 Dec 13, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode chore(vscode): Add settings of vscode Oct 20, 2017
scripts fix(package): Fix published version with selamntic-release Nov 5, 2017
src fix(directives): fire ngDoCheck hook for dynamic directives Dec 13, 2018
.codeclimate.yml chore(codeclimate): Add codecalimate config and badge to readme Feb 19, 2017
.editorconfig Initial commit Feb 16, 2017
.gitignore test(karma): Add tests support Feb 18, 2017
.npmignore fix(package): Remove src folder from published package Nov 3, 2017
.prettierrc.json chore(prettier): remove parser config prop Oct 5, 2018
.travis.yml ci(travis): Add build explicitly to test stage Apr 19, 2018
CODE_OF_CONDUCT.md docs: Create code of conduct file Mar 24, 2018
LICENSE Initial commit Feb 16, 2017
README.MD docs(readme): add angular 7 version Oct 26, 2018
karma.conf.js test(directive): Cover Dynamic directive 100% with unit tests Feb 18, 2017
package-lock.json chore(package): update dev packages Dec 13, 2018
package.json chore(package): update dev packages Dec 13, 2018
rollup.config.js build(rollup): Fix path to compiled file Nov 3, 2017
rollup.config.umd.js build(rollup): Update config according to latest changes Oct 22, 2017
tsconfig.es2015.json fix(build): Create Flat ESM modules ES5 and ES2015 Jun 6, 2017
tsconfig.es5.json fix(build): Create Flat ESM modules ES5 and ES2015 Jun 6, 2017
tsconfig.json Initial commit Feb 16, 2017
tslint.json Initial commit Feb 16, 2017
webpack.test.js test(karma): Add tests support Feb 18, 2017
yarn.lock chore(package): add tsickle lib for build to work Oct 26, 2018

README.MD

ng-dynamic-component

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

Travis CI Coverage Code Climate Npm Npm Downloads Licence semantic-release Greenkeeper badge

Version 4.x.x supports Angular 7 (ng-dynamic-component@^4.0.0)

Version 3.x.x supports Angular 6 (ng-dynamic-component@^3.0.0)

Version 2.x.x supports Angular 5 (ng-dynamic-component@^2.0.0)

Version 1.x.x supports Angular 4 (ng-dynamic-component@^1.0.0)

Version 0.x.x supports Angular 2 (ng-dynamic-component@^0.0.0)

Installation

$ npm install ng-dynamic-component --save

Usage

Import DynamicModule with dynamic components you want to insert later:

import { DynamicModule } from 'ng-dynamic-component';
import { MyDynamicComponent1, MyDynamicComponent2 } from './my-components';

@NgModule({
  imports: [
    DynamicModule.withComponents([MyDynamicComponent1, MyDynamicComponent2])
  ]
})

DynamicComponent

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;
}

Inputs and Outputs

You can also pass inputs and outputs to your dynamic components:

@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).

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 { 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

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 { 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
  ];
}

NgComponentOutlet support

You can also use NgComponentOutlet directive from @angular/common instead of <ndc-dynamic> and apply inputs and outputs to your dynamic components:

@Component({
  selector: 'my-component',
  template: `<ng-container [ngComponentOutlet]="component"
                           [ndcDynamicInputs]="inputs"
                           [ndcDynamicOutputs]="outputs"
                           ></ng-container>`
})
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 = {...};
}

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 funtionality of this library is splitted in two pieces:

  • one - component (ndc-dynamic) that is responsible for instantianting 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 ComponentInjector (it is ndc-dynamic by default).

Thanks to this separation you are able to connect inputs/outputs and life-cycle hooks to different mechanisms of injecting dynamic components by implementing ComponentInjector and providing it via DynamicModule.withComponents(null, [here]) in second argument.

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

License

MIT © Alex Malkevich