I'm trying to use ag-grid in an Angular 2 application. Because I'm using infinite scrolling and server side filtering, I want basically all of my columns in the grid to have custom filters that I can then pass to the server where the filtering will actually be executed. While ag-grid has a relatively straight forward interface for setting up these custom filters, it is noted on the Angular 2 page in the ag-grid documentation that ag-grid made use of Angular 1 compiling and that since Angular 2 does not support compiling after application startup, none of the custom components in the grid (custom filters, cells, rows, etc.) will support any of the Angular 2 features (like two-way data binding, etc.).
So I've been searching for an Angular 2 way to dynamically load the component into the DOM element that ag-grid is inserting into its filter pop-up window. I've looked at both the DynamicComponentLoader (which is deprecated) and at a couple variations of using the ComponentResolver. Once I've got the ComponentResolver, I can call resolveComponent to get a ComponentFactory and then I can use @ViewChild to get ViewContainerRef and call createComponent on that ViewContainerRef to create my new component. However, that doesn't help me with the grid because @ViewChild won't find an element that's dynamically added to the DOM directly the way ag-grid is doing it.
Alternately, once I've got the ComponentResolver and call resolveComponent to get a ComponentFactory, I can call create on the componentFactory and pass it the injector from my ViewContainerRef and a string tag for the element I want the component inserted into and that "seems to" work, but the Component is not rendered correctly. I get similar behavior if I use DynamicComponentLoader (i.e. the component is not rendered as expected).
Is there some accepted way to load an Angular 2 Component within a particular element in the DOM?
Some code to illustrate the issue follows, which I based on the Angular 2 quickstart:
app.component.ts:
import { Component, ViewChild } from '@angular/core';
import { ComponentResolver, ViewContainerRef } from '@angular/core';
import { DynComponent } from './dyn.component';
@Component({
selector: 'my-app',
template: `
<h1>My test Angular 2 App</h1>
<input type="text" class="form-control" required
[(ngModel)]="name" >
TODO: remove this: {{name}}
<p> </p>
<div #insertPoint>
<button (click)="createDynamicComponent()">Create The Dynamic Component</button>
Inserting a new component below this.
</div>`
})
export class AppComponent {
name: string;
@ViewChild('insertPoint', {read: ViewContainerRef}) compInsertPoint: ViewContainerRef;
constructor(private componentResolver: ComponentResolver){}
createDynamicComponent(){
console.log("Creating new Component.");
//You're not supposed to manipulate the DOM like this in Angular 2,
//but this is what ag-grid is doing
var newTextSpan = document.createElement('span');
newTextSpan.innerHTML = `<div id='dynCompDiv'> </div>`;
this.compInsertPoint.element.nativeElement.appendChild(newTextSpan);
this.componentResolver.resolveComponent(DynComponent)
.then(cmpFactory => {
const ctxInjector = this.compInsertPoint.injector;
//The below commented out createComponent call will create the
//component successfully, but I can't figure out how to do the
//same thing and have the component created within the above
//<div id='dynCompDiv'>
// this.compInsertPoint.createComponent(cmpFactory, 0, ctxInjector);
//This appears to try to create the component, but the component
//is not expanded correctly (missing text, no two-way data
//binding)
cmpFactory.create(ctxInjector, null, '#dynCompDiv');
})
}
}
dyn.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'dyn-app',
template: `
<h1>My test Angular 2 App</h1>
<input type="text" class="form-control" required
[(ngModel)]="name" >
TODO: remove this: {{name}}`
})
export class DynComponent {
name: string;
}
Resulting HTML after one click of the button in the app.component (note the mismatch in the input type="text" and the following plain text starting at "TODO:")
<my-app>
<h1>My test Angular 2 App</h1>
<input class="form-control ng-untouched ng-pristine ng-invalid" required="" type="text">
TODO: remove this:
<p> </p>
<div>
<button>Create The Dynamic Component</button>
Inserting a new component below this.
<span>
<div id="dynCompDiv">
<h1>My test Angular 2 App</h1>
<input class="form-control" required="" type="text">
</div>
</span>
</div>
</my-app>
Update: Here's a Plunker with this example: Angular2 Dynamic Component
Update 2: Just a little more information about the above comment that I get similar behavior using the DynamicComponentLoader. If I read the Angular code correctly, the DynamicComponentLoader is basically doing the same thing I'm doing above. It's resolving the component to get a ComponentFactory and then using the create method on that factory. So the similar behavior makes perfect sense.
Update 3: Updated the Plunker to get it working again: Updated Plunker
I have a work-around for this issue that is sufficient to get me moving again. As noted in the commented out code above, the createComponent call on a ViewContainerRef does correctly create a component. The only downside is you can only create it as a sibling to another element, not within another element. Since ag-grid's getGui call expects a return of html of a DOM element, I can create the component as a sibling and then return the element in the getGui call. This doesn't seem like the ideal solution to me and I still question whether the create call on ComponentFactory is working correctly, but the Angular folks don't see this as a bug: Angular 2 Issue 10523
So, I don't see any palatable alternatives other than to follow this workaround.
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