I have a "dashboard" that loads configured elements.
Dashboard template has this:
<div class="dash-container" [ngGrid]="gridConfig">
<div *ngFor="let box of boxes; let i = index"
[(ngGridItem)]="box.config"
(onItemChange)="updateItem(i, $event)"
(onResize)="onResize(i, $event)"
(onDrag)="onDrag(i, $event)"
(onDragStop)="onDragStop(i,$event)"
[ngClass]="box.class"
>
<div class="handle"><h4>{{box.title}}</h4></div>
<div [innerHTML]= "box.content"></div>
</div>
</div>
Now <div [innerHTML]= "box.content"></div>
will not work because non standard elements get sanitised.
Running latest Angular 2.4.6 (RC 6).
I look at the examples i could find for dynamic components - but all i see is that they just add components to the current component - but i need them in a very specific divs like in the example above.
ComponentFactoryResolver
is often used together with @ViewChild
.
But i can't just do this inside a loop:
ngAfterViewInit() {
const dashWidgetsConf = this.widgetConfigs();
for (var i = 0; i < dashWidgetsConf.length; i++) {
const conf = dashWidgetsConf[i];
@ViewChild(conf.id, {read: ViewContainerRef}) var widgetTarget: ViewContainerRef;
var widgetComponent = this.componentFactoryResolver.resolveComponentFactory(UnitsComponent);
widgetTarget.createComponent(widgetComponent);
}
}
The @viewchild gives 'Decorators are not valid here'.
How can i load components from a conf list (in a loop) and add them inside a specific div (divs got #{{conf.id}}
) in my component?
After some research, this is the solution i came up with (works in angular 4.0.0).
Load all the ViewContainerRef
targets by id:
@ViewChildren('dynamic', {read: ViewContainerRef}) public widgetTargets: QueryList<ViewContainerRef>;
Then loop over them to get the target, create a factory for the component and call createComponent
.
Also can use the component reference to subscribe or set other component properties.
ngAfterViewInit() {
const dashWidgetsConf = this.widgetConfigs();
const widgetComponents = this.widgetComponents();
for (let i = 0; i < this.widgetTargets.toArray().length; i++) {
let conf = dashWidgetsConf[i];
let component = widgetComponents[conf.id];
if(component) {
let target = this.widgetTargets.toArray()[i];
let widgetComponent = this.componentFactoryResolver.resolveComponentFactory(component);
let cmpRef: any = target.createComponent(widgetComponent);
if (cmpRef.instance.hasOwnProperty('title')) {
cmpRef.instance.title = conf.title;
}
}
}
}
The widgetComponents
is a object {key: component}
and widgetConfigs
is where i store specific component info - like title, component id etc.
Then in template:
<div *ngFor="let box of boxes; let i = index" >
<ng-template #dynamic></ng-template>
</div>
And the order of targets is the same as in my conf ( boxes
is generated from it) - which is why i can loop through them in order and use i as index to get the correct conf and component.
After
if (cmpRef.instance.hasOwnProperty('title')) {
cmpRef.instance.title = conf.title;
}
You can add this.cd.detectChanges();
or you will have the error "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked" in your children component with Angular 6x.
If you have this pattern:
<div *ngFor="for item in items">
<!-- needs to be one per item -->
<ng-template details-directive></ng-template>
</div>
I suggest wrapping the directive in a component:
@Component({
selector: 'details-wrapper',
template: '<ng-template details-directive></ng-template>'
})
export class DetailsWrapper {
@Input item?: Item;
// Dynamically load details using the regular solution.
}
And making this your for loop:
<div *ngFor="for item in items">
<details-wrapper [item]="item"></details-wrapper>
</div>