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

Commit cd322d1

Browse files
authored
Merge pull request #130 from ghiscoding/feat/angular-component-post-render
feat(PostRender): use postRender to render an Angular Component, closes #7
2 parents e2e90e2 + 013524b commit cd322d1

11 files changed

+121
-48
lines changed

src/app/app-routing.module.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1+
import { HomeComponent } from './examples/home.component';
12
import { GridAddItemComponent } from './examples/grid-additem.component';
2-
import { GridMenuComponent } from './examples/grid-menu.component';
3+
import { GridAngularComponent } from './examples/grid-angular.component';
34
import { GridBasicComponent } from './examples/grid-basic.component';
45
import { GridClientSideComponent } from './examples/grid-clientside.component';
56
import { GridColspanComponent } from './examples/grid-colspan.component';
67
import { GridDraggableGroupingComponent } from './examples/grid-draggrouping.component';
78
import { GridEditorComponent } from './examples/grid-editor.component';
8-
import { GridEditorAngularComponent } from './examples/grid-editor-angular.component';
99
import { GridFormatterComponent } from './examples/grid-formatter.component';
1010
import { GridFrozenComponent } from './examples/grid-frozen.component';
11+
import { GridGraphqlComponent } from './examples/grid-graphql.component';
1112
import { GridGroupingComponent } from './examples/grid-grouping.component';
1213
import { GridHeaderButtonComponent } from './examples/grid-headerbutton.component';
1314
import { GridHeaderMenuComponent } from './examples/grid-headermenu.component';
1415
import { GridLocalizationComponent } from './examples/grid-localization.component';
16+
import { GridMenuComponent } from './examples/grid-menu.component';
1517
import { GridOdataComponent } from './examples/grid-odata.component';
16-
import { GridGraphqlComponent } from './examples/grid-graphql.component';
1718
import { GridRemoteComponent } from './examples/grid-remote.component';
1819
import { GridRowDetailComponent } from './examples/grid-rowdetail.component';
1920
import { GridRowMoveComponent } from './examples/grid-rowmove.component';
2021
import { GridRowSelectionComponent } from './examples/grid-rowselection.component';
2122
import { GridStateComponent } from './examples/grid-state.component';
22-
import { HomeComponent } from './examples/home.component';
2323
import { SwtCommonGridTestComponent } from './examples/swt-common-grid-test.component';
2424

2525
import { NgModule } from '@angular/core';
@@ -28,11 +28,11 @@ import { TranslateModule } from '@ngx-translate/core';
2828

2929
const routes: Routes = [
3030
{ path: 'home', component: HomeComponent },
31+
{ path: 'angular-components', component: GridAngularComponent },
3132
{ path: 'additem', component: GridAddItemComponent },
3233
{ path: 'basic', component: GridBasicComponent },
3334
{ path: 'colspan', component: GridColspanComponent },
3435
{ path: 'editor', component: GridEditorComponent },
35-
{ path: 'editor-angular', component: GridEditorAngularComponent },
3636
{ path: 'formatter', component: GridFormatterComponent },
3737
{ path: 'frozen', component: GridFrozenComponent },
3838
{ path: 'headerbutton', component: GridHeaderButtonComponent },

src/app/app.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
<a [routerLink]="['/rowdetail']">21- Row Detail View</a>
9393
</li>
9494
<li routerLinkActive="active">
95-
<a [routerLink]="['/editor-angular']">22- Editors Angular Components</a>
95+
<a [routerLink]="['/angular-components']">22- Editors Angular Components</a>
9696
</li>
9797
</ul>
9898
</div>

src/app/app.module.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ 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';
1516
import { GridClientSideComponent } from './examples/grid-clientside.component';
1617
import { GridColspanComponent } from './examples/grid-colspan.component';
1718
import { GridDraggableGroupingComponent } from './examples/grid-draggrouping.component';
1819
import { GridEditorComponent } from './examples/grid-editor.component';
19-
import { GridEditorAngularComponent } from './examples/grid-editor-angular.component';
20+
import { GridAngularComponent } from './examples/grid-angular.component';
2021
import { GridFormatterComponent } from './examples/grid-formatter.component';
2122
import { GridFrozenComponent } from './examples/grid-frozen.component';
2223
import { GridGraphqlComponent } from './examples/grid-graphql.component';
@@ -71,14 +72,15 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
7172
@NgModule({
7273
declarations: [
7374
AppComponent,
75+
CustomTitleFormatterComponent,
7476
EditorNgSelectComponent,
7577
GridAddItemComponent,
78+
GridAngularComponent,
7679
GridBasicComponent,
7780
GridClientSideComponent,
7881
GridColspanComponent,
7982
GridDraggableGroupingComponent,
8083
GridEditorComponent,
81-
GridEditorAngularComponent,
8284
GridFormatterComponent,
8385
GridFrozenComponent,
8486
GridGraphqlComponent,
@@ -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: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Editor,
66
EditorValidator,
77
EditorValidatorOutput,
8+
GridOption,
89
} from './../modules/angular-slickgrid';
910

1011
/*
@@ -21,13 +22,21 @@ export class CustomAngularComponentEditor implements Editor {
2122
/** default item object */
2223
defaultItem: any;
2324

25+
/** SlickGrid grid object */
26+
grid: any;
27+
2428
constructor(private args: any) {
29+
this.grid = args && args.grid;
2530
this.init();
2631
}
2732

28-
/** Angular Util Service */
33+
/** Angular Util Service (could be inside the Grid Options Params or the Editor Params ) */
2934
get angularUtilService(): AngularUtilService {
30-
return this.columnDef && this.columnDef && this.columnDef.internalColumnEditor && this.columnDef.internalColumnEditor.params.angularUtilService;
35+
let angularUtilService = this.gridOptions && this.gridOptions.params && this.gridOptions.params.angularUtilService;
36+
if (!angularUtilService || !(angularUtilService instanceof AngularUtilService)) {
37+
angularUtilService = this.columnEditor && this.columnEditor.params && this.columnEditor.params.angularUtilService;
38+
}
39+
return angularUtilService;
3140
}
3241

3342
/** Get the Collection */
@@ -45,8 +54,13 @@ export class CustomAngularComponentEditor implements Editor {
4554
return this.columnDef && this.columnDef.internalColumnEditor || {};
4655
}
4756

57+
/** Getter for the Grid Options pulled through the Grid Object */
58+
get gridOptions(): GridOption {
59+
return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {};
60+
}
61+
4862
get hasAutoCommitEdit() {
49-
return this.args.grid.getOptions().autoCommitEdit;
63+
return this.gridOptions.autoCommitEdit;
5064
}
5165

5266
/** Get the Validator function, can be passed in Editor property or Column Definition */
@@ -55,12 +69,15 @@ export class CustomAngularComponentEditor implements Editor {
5569
}
5670

5771
init() {
58-
if (!this.columnEditor || !this.columnEditor.params.component) {
72+
if (!this.columnEditor || !this.columnEditor.params.component || !(this.angularUtilService instanceof AngularUtilService)) {
5973
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: [...] },`);
74+
You also need to provide the "AngularUtilService" via the Editor Params OR the Grid Options Params
75+
Example: this.columnDefs = [{ id: 'title', field: 'title', editor: { model: CustomAngularComponentEditor, collection: [...] }, params: { component: MyComponent, angularUtilService: this.angularUtilService }];
76+
OR this.columnDefs = [{ id: 'title', field: 'title', editor: { model: CustomAngularComponentEditor, collection: [...] }]; this.gridOptions = { params: { angularUtilService: this.angularUtilService }}`);
6177
}
6278
if (this.columnEditor && this.columnEditor.params.component) {
63-
this.componentRef = this.columnEditor.params.angularUtilService.createAngularComponentAppendToDom(this.columnEditor.params.component, this.args.container);
79+
const componentOutput = this.angularUtilService.createAngularComponentAppendToDom(this.columnEditor.params.component, this.args.container);
80+
this.componentRef = componentOutput && componentOutput.componentRef;
6481
Object.assign(this.componentRef.instance, { collection: this.collection });
6582

6683
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 renamed to src/app/examples/grid-angular.component.ts

Lines changed: 48 additions & 23 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,24 +14,29 @@ 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

2425
@Component({
25-
templateUrl: './grid-editor-angular.component.html'
26+
templateUrl: './grid-angular.component.html'
2627
})
2728
@Injectable()
28-
export class GridEditorAngularComponent implements OnInit {
29-
title = 'Example 22: Editors with Angular Components';
29+
export class GridAngularComponent implements OnInit {
30+
title = 'Example 22: Multiple Angular Components';
3031
subTitle = `
31-
Grid with Inline Editors and onCellClick actions (<a href="https://github.com/ghiscoding/Angular-Slickgrid/wiki/Editors" target="_blank">Uncyclo docs</a>).
32+
Grid with usage of Angular Components as Editor &amp; AsyncPostRender (similar to Formatter).
3233
<ul>
33-
<li>Support of Angular Component as Custom Editor (click on "Assignee" column)</li>
34-
<li>The column "Assignee" shown below uses <a href="https://github.com/ng-select/ng-select" target="_blank">ng-select</a> as a custom editor with Angular Component
35-
<li>Increased rowHeight to 45 so that the "ng-select" fits in the cell. Ideally it would be better to override the ng-select component styling to change it's max height</li>
34+
<li>Support of Angular Component as Custom Editor (click on any "Assignee" name cell)</li>
35+
<ul>
36+
<li>That column uses <a href="https://github.com/ng-select/ng-select" target="_blank">ng-select</a> as a custom editor as an Angular Component
37+
<li>Increased Grid Options "rowHeight" to 45 so that the "ng-select" fits in the cell. Ideally it would be better to override the ng-select css styling to change it's max height</li>
38+
</ul>
39+
<li>The 2nd "Assignee" column (showing in bold text) uses "asyncPostRender" with an Angular Component</li>
3640
</ul>
3741
`;
3842

@@ -52,7 +56,7 @@ export class GridEditorAngularComponent implements OnInit {
5256
{ id: '3', name: 'Paul' },
5357
];
5458

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

5761
ngOnInit(): void {
5862
this.prepareGrid();
@@ -90,14 +94,13 @@ export class GridEditorAngularComponent implements OnInit {
9094
type: FieldType.string,
9195
formatter: Formatters.complexObject,
9296
params: {
93-
complexField: 'assignee.name'
97+
complexField: 'assignee.name',
9498
},
9599
exportWithFormatter: true,
96100
editor: {
97101
model: CustomAngularComponentEditor,
98102
collection: this.assignees,
99103
params: {
100-
angularUtilService: this.angularUtilService,
101104
component: EditorNgSelectComponent,
102105
}
103106
},
@@ -106,19 +109,26 @@ export class GridEditorAngularComponent implements OnInit {
106109
this.alertWarning = `Updated Title: ${args.dataContext.title}`;
107110
}
108111
}, {
109-
id: 'duration',
110-
name: 'Duration (days)',
111-
field: 'duration',
112+
id: 'assignee2',
113+
name: 'Assignee with Angular Component',
114+
field: 'assignee',
112115
minWidth: 100,
113116
filterable: true,
114117
sortable: true,
115-
type: FieldType.number,
116-
filter: { model: Filters.slider, params: { hideSliderNumber: false } },
117-
editor: {
118-
model: Editors.slider,
119-
minValue: 0,
120-
maxValue: 100,
121-
}
118+
type: FieldType.string,
119+
120+
// loading formatter, text to display while Post Render gets processed
121+
formatter: () => '...',
122+
123+
// to load an Angular Component, you cannot use a Formatter since Angular needs at least 1 cycle to render everything
124+
// you can use a PostRenderer but you will visually see the data appearing,
125+
// which is why it's still better to use regular Formatter (with jQuery if need be) instead of Angular Component
126+
asyncPostRender: this.renderAngularComponent.bind(this),
127+
params: {
128+
component: CustomTitleFormatterComponent,
129+
angularUtilService: this.angularUtilService,
130+
},
131+
exportWithFormatter: true,
122132
}, {
123133
id: 'complete',
124134
name: '% Complete',
@@ -201,11 +211,16 @@ export class GridEditorAngularComponent implements OnInit {
201211
enableColumnPicker: true,
202212
enableExcelCopyBuffer: true,
203213
enableFiltering: true,
214+
enableAsyncPostRender: true, // for the Angular PostRenderer, don't forget to enable it
215+
asyncPostRenderDelay: 0, // also make sure to remove any delay to render it
204216
editCommandHandler: (item, column, editCommand) => {
205217
this._commandQueue.push(editCommand);
206218
editCommand.execute();
207219
},
208-
i18n: this.translate
220+
i18n: this.translate,
221+
params: {
222+
angularUtilService: this.angularUtilService // provide the service to all at once (Editor, Filter, AsyncPostRender)
223+
}
209224
};
210225

211226
this.dataset = this.mockData(NB_ITEMS);
@@ -283,4 +298,14 @@ export class GridEditorAngularComponent implements OnInit {
283298
this.gridObj.gotoCell(command.row, command.cell, false);
284299
}
285300
}
301+
302+
renderAngularComponent(cellNode: HTMLElement, row: number, dataContext: any, colDef: Column) {
303+
if (colDef.params.component) {
304+
const componentOutput = this.angularUtilService.createAngularComponent(colDef.params.component);
305+
Object.assign(componentOutput.componentRef.instance, { item: dataContext });
306+
307+
// use a delay to make sure Angular ran at least a full cycle and it finished rendering the Component
308+
setTimeout(() => $(cellNode).empty().html(componentOutput.domElement));
309+
}
310+
}
286311
}

src/app/modules/angular-slickgrid/extensions/rowDetailViewExtension.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,12 +324,14 @@ export class RowDetailViewExtension implements Extension {
324324
private renderViewModel(item: any) {
325325
const containerElements = document.getElementsByClassName(`${ROW_DETAIL_CONTAINER_PREFIX}${item.id}`);
326326
if (containerElements && containerElements.length) {
327-
const compRef = this.angularUtilService.createAngularComponentAppendToDom(this._viewComponent, containerElements[0]);
328-
Object.assign(compRef.instance, { model: item });
327+
const componentOutput = this.angularUtilService.createAngularComponentAppendToDom(this._viewComponent, containerElements[0]);
328+
if (componentOutput && componentOutput.componentRef && componentOutput.componentRef.instance) {
329+
Object.assign(componentOutput.componentRef.instance, { model: item });
329330

330-
const viewObj = this._views.find((obj) => obj.id === item.id);
331-
if (viewObj) {
332-
viewObj.componentRef = compRef;
331+
const viewObj = this._views.find((obj) => obj.id === item.id);
332+
if (viewObj) {
333+
viewObj.componentRef = componentOutput.componentRef;
334+
}
333335
}
334336
}
335337
}
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)