Fork of ng-dynamic-component with release for angular12 + rxjs7
Dynamic components with full life-cycle support for inputs and outputs
Angular | ng-dynamic-component | NPM package |
---|---|---|
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 |
$ npm install ng-dynamic-component --save
If you have an error like Can't bind to 'ndcDynamicInputs' since it isn't a known property of 'ndc-dynamic' in your IDE, but the project compiles just fine, you might want to try installing the no-barrels version instead.
$ npm install --save ng-dynamic-component@no-barrels
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;
}
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 = {...};
}
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).
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.
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 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',
};
}
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
];
}
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 tondc-dynamic
) that is responsible for carrying inputs/outputs to/from dynamic component by the help of so calledDynamicComponentInjector
.
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.
MIT © Alex Malkevich