Angular library for easy use dynamic components
npm install ng-torque- [x] AOT support
- [x] IVY-render support
- [x] Full lifecycle components
- [x] Dynamic content projection
- [x] Dynamic FormControl binding
``bash`
$ npm install ng-torque --save
`ts`
@NgModule({
declarations: [
AppComponent,
AComponent
],
imports: [
BrowserModule,
NgTorqueModule,
],
providers: [
provideDynamicEntities([AComponent])
],
entryComponents: [],
bootstrap: [AppComponent]
})
#### Simple
Then in your component's template include
`ts
@Component({
selector: 'app-test',
template:
>
,`
styles: []
})
export class TestComponent {
public dynamicEntity: Type
}
#### Binding Input/Output
You can also pass input and output to your dynamic component
AComponent
`ts
@Component({
selector: 'a',
template:
a works!
{{title}}: {{counter}}
})
export class AComponent {
@Input() public title!: string;
@Input() public counter!: number;
@Output() public count: EventEmitter = new EventEmitter(); public increase() {
this.count.emit(this.counter += 1);
}
}
`TestComponent
Create variable dynamicEntity: ComponentDynamicEntity with type:
AComponent,
input with same fields in AComponent, and use callOutput Pipe for binding output handler increseCounter.Arg
$event in pipe use for map EventEmmiter value, i.e this equal:`ts
``ts
@Component({
selector: 'app-test',
template: ,
styles: []
})
export class TestComponent {
public parentValue = 'HelloWorld';
public dynamicEntity: ComponentDynamicEntity = new ComponentDynamicEntity(
{
type: AComponent,
input: {
title: 'Dynamic Title',
counter: 0
}
}
); public increaseCounter(value: number, parentValue: string) {
console.log(value, parentValue);
}
}
`
#### Dynamic Content
You can also use content projection for your dynamic component.If you need resolve
@ContentChild/ @ContentChildren in your dynamic component - use viewQuery directive.ViewQueryDirective
If you use the component not only for dynamics, you must use
@Optional() for inject ViewQuery.`ts
@Directive({
selector: '[viewQuery]',
exportAs: 'viewQuery'
})
export class Query1Directive extends ViewQueryDirective {
@ContentChild(CComponent, {static: true}) public cComponent!: CComponent; constructor(@Optional() @Host() public wrapperComponent: DynamicWrapperComponent) {
super(wrapperComponent);
}
}
`CComponent
`ts
@Component({
selector: 'app-c',
template:
CComponent : {{title}}
,
styles: []
})
export class CComponent {
@Input() public title!: string;
}
`
AComponentUse function
provideViewQuery for provide yours ViewQuery implementation.
If you use the component not only for dynamics, you must use @Optional() for inject ViewQuery.`ts
@Component({
selector: 'a',
template:
a works!
,
providers: [
provideViewQuery(Query1Directive)
]
})
export class AComponent implements AfterContentInit {
constructor(@Optional() private contentQuery: Query1Directive) {
} public ngAfterContentInit(): void {
// Set title in CComponent
this.contentQuery.cComponent.title = 'Hello from AComponent';
}
}
`It's example equal native content-projection:
`ts
`#### Dynamic Control
You can use
dynamicControl directive for binding FormControl on your dynamic component which implements ControlValueAccessor.BComponent
`ts@Component({
selector: 'b',
template:
B-COMPONENT: {{title}}
,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => BComponent),
multi: true
},
]
})
export class BComponent implements OnInit, ControlValueAccessor, Validator {
@Input() title!: string;
public control = new FormControl(); private validateFn = () => {
};
private change = (value: any) => {
};
ngOnInit() {
this.control.valueChanges.subscribe(value => {
this.change(value);
});
}
registerOnChange(fn: any): void {
this.change = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
}
writeValue(obj: any): void {
this.control.setValue(obj, {emitEvent: false});
}
registerOnValidatorChange(fn: () => void): void {
this.validateFn = fn;
}
validate(control: AbstractControl): ValidationErrors | null {
console.log(control.value);
return Validators.required(control);
}
}
`
TestComponent`ts
dynamicControl
[control]="control"
componentResolver
[type]="entityB.type"
>
`$3
#### Template Containers
Template container represents
TemplateRef registry for dynamic resolving in your components.Before usage need provide containers and map by functions:
`ts
@NgModule({
declarations: [
AppComponent,
AComponent
],
imports: [
BrowserModule,
NgTorqueModule,
],
providers: [
provideTemplateContainer({
key: 'container',
value: ContainerComponent
}),
provideMapTypeTemplate()
],
entryComponents: [],
bootstrap: [AppComponent]
})
`
ContainerComponent`ts
@Component({
selector: 'container',
template:
})
export class ContainerComponent implements ITemplateContainer {
@ViewChild('greeting', {static: true}) public greeting!: TemplateRef; resolveTemplateRef(): { [p: string]: TemplateRef } {
return {
greeting: this.greeting
};
}
}
`
TestComponent`ts
@Component({
selector: 'app-test',
template: ,
styles: []
})
export class TestComponent {
}
`
#### Utils
mapFactory
Factory function for provide anything MAP-structure values.
For example provide map of dynamic components:
`ts
@NgModule({
declarations: [
AppComponent,
AComponent,
BComponent
],
imports: [
BrowserModule,
NgTorqueModule
],
providers: [
provideMapValue('dynamic-component', {
value: AComponent,
key: 'a-component'
}),
provideMapValue('dynamic-component', {
value: BComponent,
key: 'b-component'
}),
provideMap('dynamic-component', 'map-dynamic-component')
],
entryComponents: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(@Inject('map-dynamic-component') map: Map>) {
console.log(map);
/*
* a-component => class AComponent
* b-component => class BComponent
/
}
}
``