In Angular 2, component creation via ComponentFactory does not seem to fully display

I am trying to use ag-grid in an Angular 2 application. Since I use endless scrolling and filtering on the server side, I want basically all of my columns in the grid to have custom filters that can then be passed to the server on which the filtering will actually be performed . Although ag-grid has a relatively straightforward interface for configuring these custom filters, the ag-grid page in the ag-grid documentation notes that ag-grid uses Angular 1 compilation, and with Angular 2 it does not support compilation after application startup, none of the custom components in the grid (custom filters, cells, rows, etc.) Does not support any of the Angular 2 functions (for example, two-way data binding, etc.).

So, I was looking for a way for Angular 2 to dynamically load a component into a DOM element that ag-grid inserts into a filter popup. I looked at both DynamicComponentLoader (which is deprecated) and several use cases for ComponentResolver. Once I have a ComponentResolver, I can call resolveComponent to get the ComponentFactory, and then I can use @ViewChild to get the ViewContainerRef and call createComponent in this ViewContainerRef to create my new component. However, this does not help me with the grid, because @ViewChild will not find the element dynamically added to the DOM directly, as the ag-grid does.

Alternatively, when I have a ComponentResolver component and the resolveComponent method is called to get the ComponentFactory, I can call create on componentFactory and pass it the injector from my ViewContainerRef and a string tag for the element that I want the component to be inserted into and that "seems "works, but the component does not display correctly. I get similar behavior if I use DynamicComponentLoader (i.e. the Component does not display as expected).

Is there any acceptable way to load an Angular 2 component within a specific element in the DOM?

Below is the code to illustrate the problem that I based on 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; } 

HTML result after one button click in app.component (note the inconsistency in input type = "text" and the following simple text starting with "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 is Plunker with this example: Angular2 Dynamic Component

Update 2 . A bit more information on the above comment that I get similar behavior using DynamicComponentLoader. If I read the Angular code correctly, DynamicComponentLoader basically does the same thing as I do above. It resolves the component to get a ComponentFactory, and then uses the create method in the factory. So this kind of behavior makes sense.

Update 3 . Updated Plunker to work again. Updated Plunker

+5
source share
2 answers

I have a workaround for this problem, which is enough to make me move again. As noted in the code above, calling createComponent on the ViewContainerRef correctly creates the component. The only drawback is that you can only create it as a separate element, and not in another element. Since the ag-grid getGui call expects the html of the DOM element to return, I can create the component as a sibling and then return the element to the getGui call. This doesn't seem like the perfect solution for me, and I still doubt whether the create call on ComponentFactory works correctly, but Angular people don't see this as an error: Angular 2 Problem 10523

So, I see no acceptable alternatives other than following this workaround.

+1
source

After you create the component from the component factory, you must attach the component view to ApplicationRef so that change detection works properly 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 release https://github.com/angular/angular/issues/10523

0
source

All Articles