Reputation: 587
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
Upvotes: 5
Views: 4872
Reputation: 380
After creating the component from the component factory, you need to attach the component's view to the ApplicationRef
for change detection to work as expected on the created component.
constructor(private appRef: ApplicationRef){}
...
this.compRef = cmpFactory.create(...);
this.appRef.attachView(this.compRef.hostView);
...
ngOnDestroy() {
if(this.compRef) {
this.appRef.detachView(this.compRef.hostView);
}
}
Details can be found in this issue https://github.com/angular/angular/issues/10523
Upvotes: 0
Reputation: 587
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.
Upvotes: 1