I'm trying to create a table component which would build up a table from a column description and raw data.
I was able to create the basic functionality without a problem, but I would also like to be able to override the default rendering of a single cell in order to be able to show custom content ( for example, to show links ).
In a similar fashion that is used in DataTables, with the only difference being, that I would like to be able to pass in a name for a custom component with parameters.
My initial attempt was something like this, but the problem arises that if I instantiate the component in the display function of the column, I can't show it on the page.
@Component({
selector: 'gt-table',
template: `
<table class="table table-striped table-hover">
<thead>
<tr>
<th *ngFor="#column of columns">
{{ column.title }}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="#row of data">
<td *ngFor="#column of columns">
{{ displayCell(row, column) }}
</td>
</tr>
</tbody>
</table>
`
})
export class GTTableComponent implements {
@Input() data:Array<any>;
@Input() columns:Array<any>;
displayCell(row, column) {
if (column.display !== undefined) {
return column.display(row[column.name], row);
} else {
return row[column.name];
}
}
}
After searching on the internet I was able to find out that in order to dynamically load a component in a view you need to use DynamicComponentLoader.
After a lot of headaches and searches I was able to find something similar to what I'm looking for in this response, even though it's not the prettiest.
My only question that remains is how can I pass in parameters? I wasn't able to find a single reference regarding loading components dynamically with parameters. Any help or advise?
Solution:
So the solution is to use DynamicComponentLoader
with it's loadIntoLocation
function. The loadAsRoot
would be more ideal, but as of the state of 2.0.0-beta.7
it's buggy and you aren't able to set the instance property from the promise.
So what I did is create a component for cells in which I decide if I need to instantiate a default display component or the user wants to handle it:
import {Component, Input, OnInit, ElementRef, DynamicComponentLoader,
ComponentRef} from 'angular2/core';
import {GTTableDefaultDisplayComponent} from './gt-tabledefaultdisplay.component';
@Component({
selector: '[gt-cell]',
template: `<div #content></div>`,
directives: []
})
export class GTTableCellComponent implements OnInit {
@Input() row:any;
@Input() column:any;
constructor(private _loader: DynamicComponentLoader, private _elementRef: ElementRef) {
}
ngOnInit() {
if (this.column.display !== undefined) {
this.column.display(this._loader, this._elementRef, 'content',
this.row[this.column.name],this.row);
} else {
this._loader.loadIntoLocation(GTTableDefaultDisplayComponent,
this._elementRef, 'content').then((compRef:ComponentRef) => {
compRef.instance['content'] = this.row[this.column.name];
});
}
}
}
So the way to load a dynamic component with property is to set it in the promise returned by using compRef:ComponentRef
. This will be a variable in which you can access the instance of the newly created component with compRef.instance
and use it as an array to set its variables.
My default view component is as simple as this: import {Component, Input} from 'angular2/core';
import {GTTableLinkComponent} from './gt-tablelink.component';
@Component({
selector: 'default-display',
template: `<div>{{content}}</div>`,
directives: []
})
export class GTTableDefaultDisplayComponent {
@Input() content:string;
constructor() {
}
}
I hope this will help others too.
You can use the promise returned by the loadAsRoot
(or loadNextToLocation
or loadIntoLocation
) method to have access to the newly component instance and set elements on it:
dcl.loadAsRoot(ChildComponent, '#child',
injector).then((compRef:ComponentRef) => {
// For example
compRef.param1 = 'something';
// In your case
compRef.data = [
{ ... },
{ ... },
{ ... }
];
compRef.columns = [
'column1', 'column2'
];
});
DynamicComponentLoader
's methods return Promise<ComponentRef>
s.
The ComponentRef
in the Promise<ComponentRef>
has an instance
field. Use it to change your newly created component's attributes.
import { Component, DynamicComponentLoader, ViewContainerRef,
ViewChild } from '@angular/core';
import { bootstrap } from '@angular/platform-browser-dynamic';
@Component({
selector: 'loaded-with-var-component',
template: '~~LOADED COMPONENT WITH VAR: {{ varValue }}~~'
})
class LodadedWithVarComponent {
varValue: String = "defaultVarValue";
}
@Component({
selector: 'my-app',
template: '(<span #myVar></span>)<br>Parent'
})
class MyApp {
@ViewChild('myVar', {read: ViewContainerRef}) myVar:ViewContainerRef;
constructor(private dcl: DynamicComponentLoader) {
}
ngAfterViewInit() {
this.dcl.loadNextToLocation(LodadedWithVarComponent, this.myVar)
.then((c:ComponentRef) => {
c.instance.varValue = 'changed-var-value'; // <-- This is where the magic happens!
});
}
}
bootstrap(MyApp);
And that's it!
import {DynamicComponentLoader, ElementRef, Component, Input, View} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
@Component({
selector: 'child-component',
template: 'Child {{ stuff }}'
})
class ChildComponent {
stuff: String = "defaultValue";
}
@Component({
selector: 'my-app',
template: 'Parent (<div #child></div>)'
})
class MyApp {
constructor(private dcl: DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit() {
this.dcl.loadIntoLocation(ChildComponent, this.elementRef, 'child')
.then((c:ComponentRef) => {
c.instance.stuff = 'something'; // <------------ This is where the magic happens!
});
}
}
bootstrap(MyApp);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With