Dynamic components with full life-cycle support for inputs and outputs
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
)
$ npm install ng-dynamic-component --save
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])
]
})
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;
}
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).
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 ...
}
}
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',
};
}
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
];
}
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 = {...};
}
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 tondc-dynamic
) that is responsible for carrying inputs/outputs to/from dynamic component by the help of so calledComponentInjector
(it isndc-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.
MIT © Alex Malkevich