# ng-dynamic-component > Dynamic components with full life-cycle support for inputs and outputs [![Release Workflow](https://github.com/gund/ng-dynamic-component/actions/workflows/release.yml/badge.svg)](https://github.com/gund/ng-dynamic-component/actions/workflows/release.yml) [![Test Workflow](https://github.com/gund/ng-dynamic-component/actions/workflows/test.yml/badge.svg)](https://github.com/gund/ng-dynamic-component/actions/workflows/test.yml) [![Appveyor](https://ci.appveyor.com/api/projects/status/qwfaor0nnt9l24nj/branch/master?svg=true)](https://ci.appveyor.com/project/gund/ng-dynamic-component/branch/master) [![Coverage](https://codecov.io/gh/gund/ng-dynamic-component/branch/master/graph/badge.svg?token=BjOghV9KX8)](https://codecov.io/gh/gund/ng-dynamic-component) [![Maintainability](https://api.codeclimate.com/v1/badges/df4884f0ef7b49c285d0/maintainability)](https://codeclimate.com/github/gund/ng-dynamic-component/maintainability) [![Npm](https://img.shields.io/npm/v/ng-dynamic-component.svg?maxAge=2592000)](https://badge.fury.io/js/ng-dynamic-component) [![Npm Downloads](https://img.shields.io/npm/dt/ng-dynamic-component.svg?maxAge=2592000)](https://www.npmjs.com/package/ng-dynamic-component) [![Licence](https://img.shields.io/npm/l/ng-dynamic-component.svg?maxAge=2592000)](https://github.com/gund/ng-dynamic-component/blob/master/LICENSE) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ### Hey! There is a [proposal for new API](https://github.com/gund/ng-dynamic-component/discussions/484)! So if you are using this library please give your vote/feedback.
Compatibility with Angular | Angular | ng-dynamic-component | NPM package | | -------- | -------------------- | ------------------------------ | | >=14.1.3 | 10.3.1 | `ng-dynamic-component@^10.3.1` | | >=14.x.x | 10.2.x | `ng-dynamic-component@^10.2.0` | | 13.x.x | 10.1.x | `ng-dynamic-component@~10.1.0` | | 12.x.x | 9.x.x | `ng-dynamic-component@^9.0.0` | | 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 ```bash $ npm install ng-dynamic-component --save ``` ## Usage ### DynamicComponent Import `DynamicModule` where you need to render dynamic components: ```ts import { DynamicModule } from 'ng-dynamic-component'; @NgModule({ imports: [DynamicModule], }) export class MyModule {} ``` Then in your component's template include `` where you want to render component and bind from your component class type of component to render: ```ts @Component({ selector: 'my-component', template: ` `, }) class MyComponent { component = Math.random() > 0.5 ? MyDynamicComponent1 : MyDynamicComponent2; } ``` #### Standalone API **Since vv10.7.0** You may use `` as a standalone component: ```ts import { DynamicComponent } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, imports: [DynamicComponent], standalone: true, }) class MyComponent { component = Math.random() > 0.5 ? MyDynamicComponent1 : MyDynamicComponent2; } ``` _NOTE:_ Hovewer you should be aware that this will only import `` into your component and nothing else so things like dynamic inputs/outputs will not work and you will have to import them separately (see their respective sections). If you still need to use both `` and dynamic inputs/outputs it is recommended to keep using `DynamicModule` API. ### NgComponentOutlet You can also use [`NgComponentOutlet`](https://angular.io/api/common/NgComponentOutlet) directive from `@angular/common` instead of ``. Import `DynamicIoModule` where you need to render dynamic inputs: ```ts import { DynamicIoModule } from 'ng-dynamic-component'; @NgModule({ imports: [DynamicIoModule], }) export class MyModule {} ``` Now apply `ndcDynamicInputs` and `ndcDynamicOutputs` to `ngComponentOutlet`: ```ts @Component({ selector: 'my-component', template: `` }) class MyComponent { component = MyDynamicComponent1; inputs = {...}; outputs = {...}; } ``` Also you can use `ngComponentOutlet` with `*` syntax: ```ts @Component({ selector: 'my-component', template: `` }) class MyComponent { component = MyDynamicComponent1; inputs = {...}; outputs = {...}; } ``` #### Standalone API **Since vv10.7.0** You may use dynamic inputs/outputs with `*ngComponentOutlet` as a standalone API: ```ts import { ComponentOutletInjectorModule } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: `` imports: [ComponentOutletInjectorModule], standalone: true, }) class MyComponent { component = MyDynamicComponent1; inputs = {...}; outputs = {...}; } ``` If you want to use standard dynamic inputs/outputs with `ngComponentOutlet` as a standalone API you need to add the `DynamicIoDirective` to your imports: ```ts import { DynamicIoDirective, ComponentOutletInjectorModule } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: `` imports: [DynamicIoDirective, ComponentOutletInjectorModule], standalone: true, }) 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: ```ts @Component({ selector: 'my-component', template: ` `, }) 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(); } ``` 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). #### Standalone API **Since vv10.7.0** You can use standalone API to pass dynamic inputs/outputs using `DynamicIoDirective` with `DynamicComponent` or `ngComponentOutlet`: ```ts import { DynamicIoDirective, DynamicComponent } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, imports: [DynamicIoDirective, DynamicComponent] }) class MyComponent { component = MyDynamicComponent1; inputs = {...}; outputs = {...}; } ``` #### 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: []}`: ```ts @Component({ selector: 'my-component', template: ` `, }) 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 [`IoEventArgumentToken`](projects/ng-dynamic-component/src/lib/io/event-argument.ts) in DI. #### Output Handler Context **Since v10.4.0** You can specify the context (`this`) that will be used when calling the output handlers by providing either: - [`IoEventContextToken`](projects/ng-dynamic-component/src/lib/io/event-context.ts) - which will be; injected and used directly as a context value - [`IoEventContextProviderToken`](projects/ng-dynamic-component/src/lib/io/event-context.ts) - which will be provided and instantiated within the `IoService` and used as a context value. This useful if you have some generic way of retrieving a context for every dynamic component so you may encapsulate it in an Angular DI provider that will be instantiated within every component's injector; Example using your component as an output context: ```ts import { IoEventContextToken } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, providers: [ { provide: IoEventContextToken, useExisting: MyComponent, }, ], }) class MyComponent { component = MyDynamicComponent1; doSomething(event) { // Here `this` will be an instance of `MyComponent` } } ``` ### Component Creation Events You can subscribe to component creation events, being passed a reference to the `ComponentRef`: ```ts @Component({ selector: 'my-component', template: ` `, }) class MyComponent { component = MyDynamicComponent1; componentCreated(compRef: ComponentRef) { // 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: ```ts import { AttributesMap } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, }) 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: ```ts import { AttributesMap } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, }) class MyComponent { component = MyDynamicComponent1; attrs: AttributesMap = { 'my-attribute': 'attribute-value', class: 'some classes', }; } ``` #### Standalone API **Since vv10.7.0** You can use standalone API to pass dynamic inputs/outputs using `DynamicAttributesDirective` with `DynamicComponent` or `ngComponentOutlet`: ```ts import { DynamicAttributesDirective, DynamicComponent } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, imports: [DynamicAttributesDirective, DynamicComponent] }) class MyComponent { component = MyDynamicComponent1; attrs: AttributesMap = {...}; } ``` ### Directives (experimental) **Since v3.1.0** you can now declaratively set directives, via `ndcDynamicDirectives`. > **NOTE**: > There is a known issue with OnChanges hook not beign triggered on dynamic directives > since this part of functionality has been removed from the core as Angular now > supports this out of the box for dynamic components. > > In dynamic directives queries like `@ContentChild` and host decorators like `@HostBinding` > will not work due to involved complexity required to implement it (but PRs are welcome!). Import module `DynamicDirectivesModule` and then in template: ```ts import { dynamicDirectiveDef } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, }) class MyComponent { component = MyDynamicComponent1; dirs = [dynamicDirectiveDef(MyDirective)]; } ``` It's also possible to bind inputs and outputs to every dynamic directive: ```ts import { dynamicDirectiveDef } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, }) 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: ```ts class MyComponent { updateDirectiveInput() { this.directiveInputs.prop1 = 'new value'; } } ``` You can have multiple directives applied to same dynamic component (only one directive by same type): ```ts import { dynamicDirectiveDef } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, }) class MyComponent { component = MyDynamicComponent1; dirs = [ dynamicDirectiveDef(MyDirective1), dynamicDirectiveDef(MyDirective2), dynamicDirectiveDef(MyDirective3), dynamicDirectiveDef(MyDirective1), // This will be ignored because MyDirective1 already applied above ]; } ``` #### Standalone API **Since vv10.7.0** You can use standalone API to pass dynamic inputs/outputs using `DynamicDirectivesDirective` with `DynamicComponent` or `ngComponentOutlet`: ```ts import { DynamicDirectivesDirective, DynamicComponent } from 'ng-dynamic-component'; @Component({ selector: 'my-component', template: ` `, imports: [DynamicDirectivesDirective, DynamicComponent] }) class MyComponent { component = MyDynamicComponent1; dirs = [...]; } ``` ### 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]`). **Since v10.6.0**: You can provide custom NgModuleRef (`[ndcDynamicNgModuleRef]`) or EnvironmentInjector (`[ndcDynamicEnvironmentInjector]`) for your dynamic component. --- 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`](projects/ng-dynamic-component/src/lib/component-injector/token.ts). 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`](projects/ng-dynamic-component/src/lib/component-injector/token.ts) in DI. It was done to be able to reuse [`NgComponentOutlet`](https://github.com/angular/angular/commit/8578682) added in Angular 4-beta.3. To see example of how to implement custom component injector - see [`ComponentOutletInjectorDirective`](projects/ng-dynamic-component/src/lib/component-injector/component-outlet-injector.directive.ts) that is used to integrate `NgComponentOutlet` directive with inputs/outputs. ## Contributing You are welcome to contribute to this project. Simply follow the [contribution guide](/CONTRIBUTING.md). ## License MIT © [Alex Malkevich](malkevich.alex@gmail.com)