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

Commit f192650

Browse files
authored
Merge pull request #1536 from shanmukhateja/feat-template-ref
[Feature] Add TemplateRef support
2 parents aebd56f + 848b712 commit f192650

13 files changed

+306
-6
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface IDemoNgComponentEventType {
2+
cmd: string,
3+
data: any
4+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="btn-group d-block text-center">
2+
<button class="btn btn-sm btn-dark" (click)="onAction1()">Action 1</button>
3+
</div>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Component, Input, OnInit, Output } from '@angular/core';
2+
import { Subject } from 'rxjs';
3+
import { IDemoNgComponentEventType } from './demo-ng-template-ref-event-type';
4+
5+
@Component({
6+
selector: 'app-demo-ng-template-ref',
7+
templateUrl: './demo-ng-template-ref.component.html',
8+
})
9+
export class DemoNgComponent implements OnInit {
10+
11+
constructor() { }
12+
13+
@Output()
14+
emitter = new Subject<IDemoNgComponentEventType>();
15+
16+
@Input()
17+
data = {};
18+
19+
ngOnInit(): void {
20+
}
21+
22+
onAction1() {
23+
this.emitter.next({
24+
cmd: 'action1',
25+
data: this.data
26+
});
27+
}
28+
29+
ngOnDestroy() {
30+
this.emitter.unsubscribe();
31+
}
32+
33+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<ng-template #preview>
2+
<blockquote>Please click on Action button</blockquote>
3+
<p class="text-danger">You clicked on: <strong>{{ message }}</strong></p>
4+
<br />
5+
<table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="row-border hover"></table>
6+
</ng-template>
7+
<app-base-demo [pageTitle]="pageTitle" [mdIntro]="mdIntro" [mdHTML]="mdHTML" [mdTS]="mdTS" [template]="preview">
8+
</app-base-demo>
9+
10+
<ng-template #demoNg let-data="adtData" let-emitter="captureEvents">
11+
<app-demo-ng-template-ref [data]="data" (emitter)="emitter($event)"></app-demo-ng-template-ref>
12+
</ng-template>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
2+
import { ADTSettings, } from 'angular-datatables/src/models/settings';
3+
import { Subject } from 'rxjs';
4+
import { IDemoNgComponentEventType } from './demo-ng-template-ref-event-type';
5+
import { DemoNgComponent } from './demo-ng-template-ref.component';
6+
7+
@Component({
8+
selector: 'app-using-ng-template-ref',
9+
templateUrl: './using-ng-template-ref.component.html',
10+
})
11+
export class UsingNgTemplateRefComponent implements OnInit, AfterViewInit {
12+
13+
constructor() { }
14+
15+
pageTitle = 'Using Angular TemplateRef';
16+
mdIntro = 'assets/docs/advanced/using-ng-template-ref/intro.md';
17+
mdHTML = 'assets/docs/advanced/using-ng-template-ref/source-html.md';
18+
mdTS = 'assets/docs/advanced/using-ng-template-ref/source-ts.md';
19+
20+
dtOptions: ADTSettings = {};
21+
dtTrigger = new Subject();
22+
23+
@ViewChild('demoNg') demoNg: TemplateRef<DemoNgComponent>;
24+
message = '';
25+
26+
ngOnInit(): void {
27+
}
28+
29+
ngAfterViewInit() {
30+
const self = this;
31+
this.dtOptions = {
32+
ajax: 'data/data.json',
33+
columns: [
34+
{
35+
title: 'ID',
36+
data: 'id'
37+
},
38+
{
39+
title: 'First name',
40+
data: 'firstName',
41+
},
42+
{
43+
title: 'Last name',
44+
data: 'lastName'
45+
},
46+
{
47+
title: 'Actions',
48+
data: null,
49+
defaultContent: '',
50+
ngTemplateRef: {
51+
ref: this.demoNg,
52+
context: {
53+
// needed for capturing events inside <ng-template>
54+
captureEvents: self.onCaptureEvent.bind(self)
55+
}
56+
}
57+
}
58+
]
59+
};
60+
61+
// wait before loading table
62+
setTimeout(() => {
63+
this.dtTrigger.next();
64+
}, 200);
65+
}
66+
67+
68+
onCaptureEvent(event: IDemoNgComponentEventType) {
69+
this.message = `Event '${event.cmd}' with data '${JSON.stringify(event.data)}`;
70+
}
71+
72+
73+
}

demo/src/app/app.component.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ <h3>
9393
<li>
9494
<a routerLink="/advanced/using-pipe">Using Angular Pipes</a>
9595
</li>
96+
<li>
97+
<a routerLink="/advanced/using-template-ref">Using Angular TemplateRef</a>
98+
</li>
9699
</ul>
97100
</div>
98101
</li>

demo/src/app/app.module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import { UpperCasePipe } from '@angular/common';
4343
import { MarkdownModule } from 'ngx-markdown';
4444
import { BaseDemoComponent } from './base-demo/base-demo.component';
4545
import { FAQComponent } from './f-a-q/f-a-q.component';
46+
import { UsingNgTemplateRefComponent } from './advanced/using-ng-template-ref.component';
47+
import { DemoNgComponent } from './advanced/demo-ng-template-ref.component';
4648

4749
@NgModule({
4850
declarations: [
@@ -72,7 +74,9 @@ import { FAQComponent } from './f-a-q/f-a-q.component';
7274
SelectExtensionComponent,
7375
UsingNgPipeComponent,
7476
BaseDemoComponent,
75-
FAQComponent
77+
FAQComponent,
78+
UsingNgTemplateRefComponent,
79+
DemoNgComponent
7680
],
7781
imports: [
7882
BrowserModule,

demo/src/app/app.routing.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ResponsiveExtensionComponent } from './extensions/responsive-extension.
2626
import { SelectExtensionComponent } from './extensions/select-extension.component';
2727
import { UsingNgPipeComponent } from './advanced/using-ng-pipe.component';
2828
import { FAQComponent } from './f-a-q/f-a-q.component';
29+
import { UsingNgTemplateRefComponent } from './advanced/using-ng-template-ref.component';
2930

3031
const routes: Routes = [
3132
{
@@ -101,6 +102,10 @@ const routes: Routes = [
101102
path: 'advanced/using-pipe',
102103
component: UsingNgPipeComponent
103104
},
105+
{
106+
path: 'advanced/using-template-ref',
107+
component: UsingNgTemplateRefComponent
108+
},
104109
{
105110
path: 'extensions/buttons',
106111
component: ButtonsExtensionComponent
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
You can use Angular `TemplateRef` acquired from `ViewChild` or passing it from HTML to transform data on the table.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
```html
2+
3+
<!-- demo-ng-template-ref.component.html -->
4+
<div class="btn-group d-block text-center">
5+
<button class="btn btn-sm btn-dark" (click)="onAction1()">Action 1</button>
6+
</div>
7+
8+
<!-- using-ng-template-ref.component.html -->
9+
<table datatable [dtOptions]="dtOptions" class="row-border hover"></table>
10+
11+
```
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
```typescript
2+
// demo-ng-template-ref.component.ts
3+
4+
import { Component, Input, OnInit, Output } from "@angular/core";
5+
import { Subject } from "rxjs";
6+
import { IDemoNgComponentEventType } from "./demo-ng-template-ref-event-type";
7+
8+
@Component({
9+
selector: "app-demo-ng-template-ref",
10+
templateUrl: "./demo-ng-template-ref.component.html",
11+
})
12+
export class DemoNgComponent implements OnInit {
13+
constructor() {}
14+
15+
@Output()
16+
emitter = new Subject<IDemoNgComponentEventType>();
17+
18+
@Input()
19+
data = {};
20+
21+
ngOnInit(): void {}
22+
23+
onAction1() {
24+
this.emitter.next({
25+
cmd: "action1",
26+
data: this.data,
27+
});
28+
}
29+
30+
ngOnDestroy() {
31+
this.emitter.unsubscribe();
32+
}
33+
}
34+
35+
// demo-ng-template-ref-event-type.ts
36+
37+
export interface IDemoNgComponentEventType {
38+
cmd: string;
39+
data: any;
40+
}
41+
42+
// ng-template-ref.component.ts
43+
import {
44+
AfterViewInit,
45+
Component,
46+
OnInit,
47+
TemplateRef,
48+
ViewChild,
49+
} from "@angular/core";
50+
import { ADTSettings } from "angular-datatables/src/models/settings";
51+
import { Subject } from "rxjs";
52+
import { IDemoNgComponentEventType } from "./demo-ng-template-ref-event-type";
53+
import { DemoNgComponent } from "./demo-ng-template-ref.component";
54+
55+
@Component({
56+
selector: "app-using-ng-template-ref",
57+
templateUrl: "./using-ng-template-ref.component.html",
58+
})
59+
export class UsingNgTemplateRefComponent implements OnInit, AfterViewInit {
60+
constructor() {}
61+
62+
pageTitle = "Using Angular TemplateRef";
63+
mdIntro = "assets/docs/advanced/using-ng-template-ref/intro.md";
64+
mdHTML = "assets/docs/advanced/using-ng-template-ref/source-html.md";
65+
mdTS = "assets/docs/advanced/using-ng-template-ref/source-ts.md";
66+
67+
dtOptions: ADTSettings = {};
68+
dtTrigger = new Subject();
69+
70+
@ViewChild("demoNg") demoNg: TemplateRef<DemoNgComponent>;
71+
72+
ngOnInit(): void {}
73+
74+
ngAfterViewInit() {
75+
const self = this;
76+
this.dtOptions = {
77+
ajax: "data/data.json",
78+
columns: [
79+
{
80+
title: "ID",
81+
data: "id",
82+
},
83+
{
84+
title: "First name",
85+
data: "firstName",
86+
},
87+
{
88+
title: "Last name",
89+
data: "lastName",
90+
},
91+
{
92+
title: "Actions",
93+
data: null,
94+
defaultContent: "",
95+
ngTemplateRef: {
96+
ref: this.demoNg,
97+
context: {
98+
// needed for capturing events inside <ng-template>
99+
captureEvents: self.onCaptureEvent.bind(self),
100+
},
101+
},
102+
},
103+
],
104+
};
105+
106+
// wait before loading table
107+
setTimeout(() => {
108+
this.dtTrigger.next();
109+
}, 200);
110+
}
111+
112+
onCaptureEvent(event: IDemoNgComponentEventType) {
113+
console.log(event);
114+
}
115+
}
116+
```

src/angular-datatables.directive.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
* found in the LICENSE file at https://raw.githubusercontent.com/l-lin/angular-datatables/master/LICENSE
66
*/
77

8-
import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
8+
import { Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewContainerRef } from '@angular/core';
99
import { Subject } from 'rxjs';
10-
import { ADTSettings } from './models/settings';
10+
import { ADTSettings, ADTTemplateRefContext } from './models/settings';
1111

1212
@Directive({
1313
selector: '[datatable]'
@@ -37,7 +37,11 @@ export class DataTableDirective implements OnDestroy, OnInit {
3737
// Only used for destroying the table when destroying this directive
3838
private dt: DataTables.Api;
3939

40-
constructor(private el: ElementRef) { }
40+
constructor(
41+
private el: ElementRef,
42+
private vcr: ViewContainerRef,
43+
private renderer: Renderer2
44+
) { }
4145

4246
ngOnInit(): void {
4347
if (this.dtTrigger) {
@@ -59,6 +63,7 @@ export class DataTableDirective implements OnDestroy, OnInit {
5963
}
6064

6165
private displayTable(): void {
66+
const self = this;
6267
this.dtInstance = new Promise((resolve, reject) => {
6368
Promise.resolve(this.dtOptions).then(dtOptions => {
6469
// Using setTimeout as a "hack" to be "part" of NgZone
@@ -69,7 +74,7 @@ export class DataTableDirective implements OnDestroy, OnInit {
6974
if (dtOptions.columns) {
7075
const columns = dtOptions.columns;
7176
// Filter columns with pipe declared
72-
const colsWithPipe = columns.filter(x => x.ngPipeInstance);
77+
const colsWithPipe = columns.filter(x => x.ngPipeInstance && !x.ngTemplateRef);
7378
// Iterate
7479
colsWithPipe.forEach(el => {
7580
const pipe = el.ngPipeInstance;
@@ -83,6 +88,22 @@ export class DataTableDirective implements OnDestroy, OnInit {
8388
// Apply transformed string to <td>
8489
$(rowFromCol).text(rowValAfter);
8590
});
91+
92+
// Filter columns using `ngTemplateRef`
93+
const colsWithTemplate = columns.filter(x => x.ngTemplateRef && !x.ngPipeInstance);
94+
colsWithTemplate.forEach(el => {
95+
const { ref, context } = el.ngTemplateRef;
96+
// get <td> element which holds data using index
97+
const index = columns.findIndex(e => e.data == el.data);
98+
const cellFromIndex = row.childNodes.item(index);
99+
// render onto DOM
100+
// finalize context to be sent to user
101+
const _context = Object.assign({}, context, context?.userData, {
102+
adtData: data
103+
});
104+
const instance = self.vcr.createEmbeddedView(ref, _context);
105+
self.renderer.appendChild(cellFromIndex, instance.rootNodes[0]);
106+
});
86107
}
87108

88109
// run user specified row callback if provided.

src/models/settings.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1-
import { PipeTransform } from '@angular/core';
1+
import { PipeTransform, TemplateRef } from '@angular/core';
22

33
export interface ADTSettings extends DataTables.Settings {
44
columns?: ADTColumns[];
55
}
66
export interface ADTColumns extends DataTables.ColumnSettings {
77
/** Set instance of Angular pipe to transform the data of particular column */
88
ngPipeInstance?: PipeTransform;
9+
/** Set `TemplateRef` to transform the data of this column */
10+
ngTemplateRef?: ADTTemplateRef;
11+
}
12+
13+
export interface ADTTemplateRef {
14+
/** `TemplateRef` to work with */
15+
ref: TemplateRef<any>;
16+
/** */
17+
context?: ADTTemplateRefContext;
18+
}
19+
20+
export interface ADTTemplateRefContext {
21+
captureEvents: Function;
22+
userData?: any;
923
}

0 commit comments

Comments
 (0)