Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit 5b32de2

Browse files
Ghislain BeaulacGhislain Beaulac
Ghislain Beaulac
authored and
Ghislain Beaulac
committed
feat(PostRender): use postRender to render an Angular Component
- this feature kinda simulate a Formatter with an Angular Component but has some slight delay in rendering the content, that is because Angular requires at least 1 cycle to render it's own component and cannot output a string right (which SlickGrid Formatter requires to work).
1 parent bb62c0a commit 5b32de2

File tree

7 files changed

+78
-13
lines changed

7 files changed

+78
-13
lines changed

src/app/app.module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-transla
99
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
1010

1111
import { AppComponent } from './app.component';
12+
import { CustomTitleFormatterComponent } from './examples/custom-titleFormatter.component';
1213
import { EditorNgSelectComponent } from './examples/editor-ng-select.component';
1314
import { GridAddItemComponent } from './examples/grid-additem.component';
1415
import { GridBasicComponent } from './examples/grid-basic.component';
@@ -71,6 +72,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
7172
@NgModule({
7273
declarations: [
7374
AppComponent,
75+
CustomTitleFormatterComponent,
7476
EditorNgSelectComponent,
7577
GridAddItemComponent,
7678
GridBasicComponent,
@@ -125,6 +127,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
125127
],
126128
entryComponents: [
127129
// dynamically created components
130+
CustomTitleFormatterComponent,
128131
EditorNgSelectComponent,
129132
RowDetailPreloadComponent,
130133
RowDetailViewComponent,

src/app/examples/custom-angularComponentEditor.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ export class CustomAngularComponentEditor implements Editor {
5555
}
5656

5757
init() {
58-
if (!this.columnEditor || !this.columnEditor.params.component) {
58+
if (!this.columnEditor || !this.columnEditor.params.component || !(this.columnEditor.params.angularUtilService instanceof AngularUtilService)) {
5959
throw new Error(`[Angular-Slickgrid] For the Editors.angularComponent to work properly, you need to provide your component to the "component" property and make sure to add it to your "entryComponents" array.
60-
Example: this.columnDefs = [{ id: 'title', field: 'title', editor: { component: MyComponent, model: Editors.angularComponent, collection: [...] },`);
60+
Example: this.columnDefs = [{ id: 'title', field: 'title', editor: { model: Editors.angularComponent, collection: [...] }, params: { component: MyComponent, angularUtilService: this.angularUtilService }`);
6161
}
6262
if (this.columnEditor && this.columnEditor.params.component) {
63-
this.componentRef = this.columnEditor.params.angularUtilService.createAngularComponentAppendToDom(this.columnEditor.params.component, this.args.container);
63+
const angularUtilService = this.columnEditor.params.angularUtilService as AngularUtilService;
64+
const componentOutput = angularUtilService.createAngularComponentAppendToDom(this.columnEditor.params.component, this.args.container);
65+
this.componentRef = componentOutput && componentOutput.componentRef;
6466
Object.assign(this.componentRef.instance, { collection: this.collection });
6567

6668
this.componentRef.instance.onModelChanged.subscribe((item) => {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
template: `<b>{{item?.assignee?.name}}</b>`
5+
})
6+
export class CustomTitleFormatterComponent {
7+
item: any;
8+
}

src/app/examples/grid-editor-angular.component.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Component, Injectable, OnInit } from '@angular/core';
2-
import { HttpClient } from '@angular/common/http';
1+
import { Component, Injectable, OnInit, EmbeddedViewRef } from '@angular/core';
32
import { TranslateService } from '@ngx-translate/core';
43
import {
54
AngularGridInstance,
@@ -15,9 +14,11 @@ import {
1514
} from './../modules/angular-slickgrid';
1615
import { EditorNgSelectComponent } from './editor-ng-select.component';
1716
import { CustomAngularComponentEditor } from './custom-angularComponentEditor';
17+
import { CustomTitleFormatterComponent } from './custom-titleFormatter.component';
1818

1919
// using external non-typed js libraries
2020
declare var Slick: any;
21+
declare var $: any;
2122

2223
const NB_ITEMS = 100;
2324

@@ -52,7 +53,7 @@ export class GridEditorAngularComponent implements OnInit {
5253
{ id: '3', name: 'Paul' },
5354
];
5455

55-
constructor(private angularUtilService: AngularUtilService, private http: HttpClient, private translate: TranslateService) {}
56+
constructor(private angularUtilService: AngularUtilService, private translate: TranslateService) {}
5657

5758
ngOnInit(): void {
5859
this.prepareGrid();
@@ -90,7 +91,7 @@ export class GridEditorAngularComponent implements OnInit {
9091
type: FieldType.string,
9192
formatter: Formatters.complexObject,
9293
params: {
93-
complexField: 'assignee.name'
94+
complexField: 'assignee.name',
9495
},
9596
exportWithFormatter: true,
9697
editor: {
@@ -105,6 +106,27 @@ export class GridEditorAngularComponent implements OnInit {
105106
console.log(args);
106107
this.alertWarning = `Updated Title: ${args.dataContext.title}`;
107108
}
109+
}, {
110+
id: 'assignee2',
111+
name: 'Assignee with Angular Component',
112+
field: 'assignee',
113+
minWidth: 100,
114+
filterable: true,
115+
sortable: true,
116+
type: FieldType.string,
117+
118+
// loading formatter, text to display while Post Render gets processed
119+
formatter: () => '...',
120+
121+
// to load an Angular Component, you cannot use a Formatter since Angular needs at least 1 cycle to render everything
122+
// you can use a PostRenderer but you will visually see the data appearing,
123+
// which is why it's still better to use regular Formatter (with jQuery if need be) instead of Angular Component
124+
asyncPostRender: this.renderAngularComponent.bind(this),
125+
params: {
126+
component: CustomTitleFormatterComponent,
127+
angularUtilService: this.angularUtilService,
128+
},
129+
exportWithFormatter: true,
108130
}, {
109131
id: 'duration',
110132
name: 'Duration (days)',
@@ -201,6 +223,8 @@ export class GridEditorAngularComponent implements OnInit {
201223
enableColumnPicker: true,
202224
enableExcelCopyBuffer: true,
203225
enableFiltering: true,
226+
enableAsyncPostRender: true, // for the Angular PostRenderer, don't forget to enable it
227+
asyncPostRenderDelay: 0, // also make sure to remove any delay to render it
204228
editCommandHandler: (item, column, editCommand) => {
205229
this._commandQueue.push(editCommand);
206230
editCommand.execute();
@@ -283,4 +307,14 @@ export class GridEditorAngularComponent implements OnInit {
283307
this.gridObj.gotoCell(command.row, command.cell, false);
284308
}
285309
}
310+
311+
renderAngularComponent(cellNode: HTMLElement, row: number, dataContext: any, colDef: Column) {
312+
if (colDef.params.component) {
313+
const componentOutput = this.angularUtilService.createAngularComponent(colDef.params.component);
314+
Object.assign(componentOutput.componentRef.instance, { item: dataContext });
315+
316+
// use a delay to make sure Angular ran at least a cycle and it finished rendering the Component
317+
setTimeout(() => $(cellNode).empty().html($(componentOutput.domElement).html()));
318+
}
319+
}
286320
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ComponentRef } from '@angular/core';
2+
3+
export interface AngularComponentOutput {
4+
componentRef: ComponentRef<any>;
5+
domElement: HTMLElement;
6+
}

src/app/modules/angular-slickgrid/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './aggregator.interface';
2+
export * from './angularComponentOutput.interface';
23
export * from './angularGridInstance.interface';
34
export * from './autoResizeOption.interface';
45
export * from './backendService.interface';
Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AngularComponentOutput } from './../models/angularComponentOutput.interface';
12
import { ApplicationRef, ComponentFactoryResolver, ComponentRef, EmbeddedViewRef, Injectable, Injector } from '@angular/core';
23

34
@Injectable()
@@ -9,7 +10,7 @@ export class AngularUtilService {
910
) { }
1011

1112
// ref https://hackernoon.com/angular-pro-tip-how-to-dynamically-create-components-in-body-ba200cc289e6
12-
createAngularComponentAppendToDom(component: any, targetElement?: HTMLElement | Element): ComponentRef<any> {
13+
createAngularComponent(component: any): AngularComponentOutput {
1314
// Create a component reference from the component
1415
const componentRef = this.compFactoryResolver
1516
.resolveComponentFactory(component)
@@ -19,16 +20,26 @@ export class AngularUtilService {
1920
this.appRef.attachView(componentRef.hostView);
2021

2122
// Get DOM element from component
22-
const domElem = (componentRef.hostView as EmbeddedViewRef<any>)
23-
.rootNodes[0] as HTMLElement;
23+
let domElem;
24+
const viewRef = (componentRef.hostView as EmbeddedViewRef<any>);
25+
if (viewRef && Array.isArray(viewRef.rootNodes) && viewRef.rootNodes[0]) {
26+
domElem = viewRef.rootNodes[0] as HTMLElement;
27+
}
28+
29+
return { componentRef, domElement: domElem };
30+
}
31+
32+
// ref https://hackernoon.com/angular-pro-tip-how-to-dynamically-create-components-in-body-ba200cc289e6
33+
createAngularComponentAppendToDom(component: any, targetElement?: HTMLElement | Element): AngularComponentOutput {
34+
const componentOutput = this.createAngularComponent(component);
2435

2536
// Append DOM element to the HTML element specified
2637
if (targetElement && targetElement.appendChild) {
27-
targetElement.appendChild(domElem);
38+
targetElement.appendChild(componentOutput.domElement);
2839
} else {
29-
document.body.appendChild(domElem); // when no target provided, we'll simply add it to the HTML Body
40+
document.body.appendChild(componentOutput.domElement); // when no target provided, we'll simply add it to the HTML Body
3041
}
3142

32-
return componentRef;
43+
return componentOutput;
3344
}
3445
}

0 commit comments

Comments
 (0)