diff --git a/demo/src/app/advanced/demo-ng-template-ref-event-type.ts b/demo/src/app/advanced/demo-ng-template-ref-event-type.ts new file mode 100644 index 000000000..d51d2a12f --- /dev/null +++ b/demo/src/app/advanced/demo-ng-template-ref-event-type.ts @@ -0,0 +1,4 @@ +export interface IDemoNgComponentEventType { + cmd: string, + data: any +} diff --git a/demo/src/app/advanced/demo-ng-template-ref.component.html b/demo/src/app/advanced/demo-ng-template-ref.component.html new file mode 100644 index 000000000..d86e2a7ec --- /dev/null +++ b/demo/src/app/advanced/demo-ng-template-ref.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/demo/src/app/advanced/demo-ng-template-ref.component.ts b/demo/src/app/advanced/demo-ng-template-ref.component.ts new file mode 100644 index 000000000..aa974fd07 --- /dev/null +++ b/demo/src/app/advanced/demo-ng-template-ref.component.ts @@ -0,0 +1,33 @@ +import { Component, Input, OnInit, Output } from '@angular/core'; +import { Subject } from 'rxjs'; +import { IDemoNgComponentEventType } from './demo-ng-template-ref-event-type'; + +@Component({ + selector: 'app-demo-ng-template-ref', + templateUrl: './demo-ng-template-ref.component.html', +}) +export class DemoNgComponent implements OnInit { + + constructor() { } + + @Output() + emitter = new Subject(); + + @Input() + data = {}; + + ngOnInit(): void { + } + + onAction1() { + this.emitter.next({ + cmd: 'action1', + data: this.data + }); + } + + ngOnDestroy() { + this.emitter.unsubscribe(); + } + +} diff --git a/demo/src/app/advanced/using-ng-template-ref.component.html b/demo/src/app/advanced/using-ng-template-ref.component.html new file mode 100644 index 000000000..cead8dae1 --- /dev/null +++ b/demo/src/app/advanced/using-ng-template-ref.component.html @@ -0,0 +1,12 @@ + +
Please click on Action button
+

You clicked on: {{ message }}

+
+
+
+ + + + + + diff --git a/demo/src/app/advanced/using-ng-template-ref.component.ts b/demo/src/app/advanced/using-ng-template-ref.component.ts new file mode 100644 index 000000000..f0761c214 --- /dev/null +++ b/demo/src/app/advanced/using-ng-template-ref.component.ts @@ -0,0 +1,73 @@ +import { AfterViewInit, Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { ADTSettings, } from 'angular-datatables/src/models/settings'; +import { Subject } from 'rxjs'; +import { IDemoNgComponentEventType } from './demo-ng-template-ref-event-type'; +import { DemoNgComponent } from './demo-ng-template-ref.component'; + +@Component({ + selector: 'app-using-ng-template-ref', + templateUrl: './using-ng-template-ref.component.html', +}) +export class UsingNgTemplateRefComponent implements OnInit, AfterViewInit { + + constructor() { } + + pageTitle = 'Using Angular TemplateRef'; + mdIntro = 'assets/docs/advanced/using-ng-template-ref/intro.md'; + mdHTML = 'assets/docs/advanced/using-ng-template-ref/source-html.md'; + mdTS = 'assets/docs/advanced/using-ng-template-ref/source-ts.md'; + + dtOptions: ADTSettings = {}; + dtTrigger = new Subject(); + + @ViewChild('demoNg') demoNg: TemplateRef; + message = ''; + + ngOnInit(): void { + } + + ngAfterViewInit() { + const self = this; + this.dtOptions = { + ajax: 'data/data.json', + columns: [ + { + title: 'ID', + data: 'id' + }, + { + title: 'First name', + data: 'firstName', + }, + { + title: 'Last name', + data: 'lastName' + }, + { + title: 'Actions', + data: null, + defaultContent: '', + ngTemplateRef: { + ref: this.demoNg, + context: { + // needed for capturing events inside + captureEvents: self.onCaptureEvent.bind(self) + } + } + } + ] + }; + + // wait before loading table + setTimeout(() => { + this.dtTrigger.next(); + }, 200); + } + + + onCaptureEvent(event: IDemoNgComponentEventType) { + this.message = `Event '${event.cmd}' with data '${JSON.stringify(event.data)}`; + } + + +} diff --git a/demo/src/app/app.component.html b/demo/src/app/app.component.html index edf8ef6b1..bd402e955 100644 --- a/demo/src/app/app.component.html +++ b/demo/src/app/app.component.html @@ -93,6 +93,9 @@

  • Using Angular Pipes
  • +
  • + Using Angular TemplateRef +
  • diff --git a/demo/src/app/app.module.ts b/demo/src/app/app.module.ts index d3c9b2dcd..6a3bcbcb9 100644 --- a/demo/src/app/app.module.ts +++ b/demo/src/app/app.module.ts @@ -43,6 +43,8 @@ import { UpperCasePipe } from '@angular/common'; import { MarkdownModule } from 'ngx-markdown'; import { BaseDemoComponent } from './base-demo/base-demo.component'; import { FAQComponent } from './f-a-q/f-a-q.component'; +import { UsingNgTemplateRefComponent } from './advanced/using-ng-template-ref.component'; +import { DemoNgComponent } from './advanced/demo-ng-template-ref.component'; @NgModule({ declarations: [ @@ -72,7 +74,9 @@ import { FAQComponent } from './f-a-q/f-a-q.component'; SelectExtensionComponent, UsingNgPipeComponent, BaseDemoComponent, - FAQComponent + FAQComponent, + UsingNgTemplateRefComponent, + DemoNgComponent ], imports: [ BrowserModule, diff --git a/demo/src/app/app.routing.ts b/demo/src/app/app.routing.ts index 726e822d4..15ef12cb1 100644 --- a/demo/src/app/app.routing.ts +++ b/demo/src/app/app.routing.ts @@ -26,6 +26,7 @@ import { ResponsiveExtensionComponent } from './extensions/responsive-extension. import { SelectExtensionComponent } from './extensions/select-extension.component'; import { UsingNgPipeComponent } from './advanced/using-ng-pipe.component'; import { FAQComponent } from './f-a-q/f-a-q.component'; +import { UsingNgTemplateRefComponent } from './advanced/using-ng-template-ref.component'; const routes: Routes = [ { @@ -101,6 +102,10 @@ const routes: Routes = [ path: 'advanced/using-pipe', component: UsingNgPipeComponent }, + { + path: 'advanced/using-template-ref', + component: UsingNgTemplateRefComponent + }, { path: 'extensions/buttons', component: ButtonsExtensionComponent diff --git a/demo/src/assets/docs/advanced/using-ng-template-ref/intro.md b/demo/src/assets/docs/advanced/using-ng-template-ref/intro.md new file mode 100644 index 000000000..29c511562 --- /dev/null +++ b/demo/src/assets/docs/advanced/using-ng-template-ref/intro.md @@ -0,0 +1 @@ +You can use Angular `TemplateRef` acquired from `ViewChild` or passing it from HTML to transform data on the table. diff --git a/demo/src/assets/docs/advanced/using-ng-template-ref/source-html.md b/demo/src/assets/docs/advanced/using-ng-template-ref/source-html.md new file mode 100644 index 000000000..6181f66d2 --- /dev/null +++ b/demo/src/assets/docs/advanced/using-ng-template-ref/source-html.md @@ -0,0 +1,11 @@ +```html + + +
    + +
    + + +
    + +``` diff --git a/demo/src/assets/docs/advanced/using-ng-template-ref/source-ts.md b/demo/src/assets/docs/advanced/using-ng-template-ref/source-ts.md new file mode 100644 index 000000000..019e33771 --- /dev/null +++ b/demo/src/assets/docs/advanced/using-ng-template-ref/source-ts.md @@ -0,0 +1,116 @@ +```typescript +// demo-ng-template-ref.component.ts + +import { Component, Input, OnInit, Output } from "@angular/core"; +import { Subject } from "rxjs"; +import { IDemoNgComponentEventType } from "./demo-ng-template-ref-event-type"; + +@Component({ + selector: "app-demo-ng-template-ref", + templateUrl: "./demo-ng-template-ref.component.html", +}) +export class DemoNgComponent implements OnInit { + constructor() {} + + @Output() + emitter = new Subject(); + + @Input() + data = {}; + + ngOnInit(): void {} + + onAction1() { + this.emitter.next({ + cmd: "action1", + data: this.data, + }); + } + + ngOnDestroy() { + this.emitter.unsubscribe(); + } +} + +// demo-ng-template-ref-event-type.ts + +export interface IDemoNgComponentEventType { + cmd: string; + data: any; +} + +// ng-template-ref.component.ts +import { + AfterViewInit, + Component, + OnInit, + TemplateRef, + ViewChild, +} from "@angular/core"; +import { ADTSettings } from "angular-datatables/src/models/settings"; +import { Subject } from "rxjs"; +import { IDemoNgComponentEventType } from "./demo-ng-template-ref-event-type"; +import { DemoNgComponent } from "./demo-ng-template-ref.component"; + +@Component({ + selector: "app-using-ng-template-ref", + templateUrl: "./using-ng-template-ref.component.html", +}) +export class UsingNgTemplateRefComponent implements OnInit, AfterViewInit { + constructor() {} + + pageTitle = "Using Angular TemplateRef"; + mdIntro = "assets/docs/advanced/using-ng-template-ref/intro.md"; + mdHTML = "assets/docs/advanced/using-ng-template-ref/source-html.md"; + mdTS = "assets/docs/advanced/using-ng-template-ref/source-ts.md"; + + dtOptions: ADTSettings = {}; + dtTrigger = new Subject(); + + @ViewChild("demoNg") demoNg: TemplateRef; + + ngOnInit(): void {} + + ngAfterViewInit() { + const self = this; + this.dtOptions = { + ajax: "data/data.json", + columns: [ + { + title: "ID", + data: "id", + }, + { + title: "First name", + data: "firstName", + }, + { + title: "Last name", + data: "lastName", + }, + { + title: "Actions", + data: null, + defaultContent: "", + ngTemplateRef: { + ref: this.demoNg, + context: { + // needed for capturing events inside + captureEvents: self.onCaptureEvent.bind(self), + }, + }, + }, + ], + }; + + // wait before loading table + setTimeout(() => { + this.dtTrigger.next(); + }, 200); + } + + onCaptureEvent(event: IDemoNgComponentEventType) { + console.log(event); + } +} +``` diff --git a/src/angular-datatables.directive.ts b/src/angular-datatables.directive.ts index 8eb4c34bf..056d10eb3 100644 --- a/src/angular-datatables.directive.ts +++ b/src/angular-datatables.directive.ts @@ -5,9 +5,9 @@ * found in the LICENSE file at https://raw.githubusercontent.com/l-lin/angular-datatables/master/LICENSE */ -import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { Subject } from 'rxjs'; -import { ADTSettings } from './models/settings'; +import { ADTSettings, ADTTemplateRefContext } from './models/settings'; @Directive({ selector: '[datatable]' @@ -37,7 +37,11 @@ export class DataTableDirective implements OnDestroy, OnInit { // Only used for destroying the table when destroying this directive private dt: DataTables.Api; - constructor(private el: ElementRef) { } + constructor( + private el: ElementRef, + private vcr: ViewContainerRef, + private renderer: Renderer2 + ) { } ngOnInit(): void { if (this.dtTrigger) { @@ -59,6 +63,7 @@ export class DataTableDirective implements OnDestroy, OnInit { } private displayTable(): void { + const self = this; this.dtInstance = new Promise((resolve, reject) => { Promise.resolve(this.dtOptions).then(dtOptions => { // Using setTimeout as a "hack" to be "part" of NgZone @@ -69,7 +74,7 @@ export class DataTableDirective implements OnDestroy, OnInit { if (dtOptions.columns) { const columns = dtOptions.columns; // Filter columns with pipe declared - const colsWithPipe = columns.filter(x => x.ngPipeInstance); + const colsWithPipe = columns.filter(x => x.ngPipeInstance && !x.ngTemplateRef); // Iterate colsWithPipe.forEach(el => { const pipe = el.ngPipeInstance; @@ -83,6 +88,22 @@ export class DataTableDirective implements OnDestroy, OnInit { // Apply transformed string to $(rowFromCol).text(rowValAfter); }); + + // Filter columns using `ngTemplateRef` + const colsWithTemplate = columns.filter(x => x.ngTemplateRef && !x.ngPipeInstance); + colsWithTemplate.forEach(el => { + const { ref, context } = el.ngTemplateRef; + // get element which holds data using index + const index = columns.findIndex(e => e.data == el.data); + const cellFromIndex = row.childNodes.item(index); + // render onto DOM + // finalize context to be sent to user + const _context = Object.assign({}, context, context?.userData, { + adtData: data + }); + const instance = self.vcr.createEmbeddedView(ref, _context); + self.renderer.appendChild(cellFromIndex, instance.rootNodes[0]); + }); } // run user specified row callback if provided. diff --git a/src/models/settings.ts b/src/models/settings.ts index 68561c1f1..1e4932c7d 100644 --- a/src/models/settings.ts +++ b/src/models/settings.ts @@ -1,4 +1,4 @@ -import { PipeTransform } from '@angular/core'; +import { PipeTransform, TemplateRef } from '@angular/core'; export interface ADTSettings extends DataTables.Settings { columns?: ADTColumns[]; @@ -6,4 +6,18 @@ export interface ADTSettings extends DataTables.Settings { export interface ADTColumns extends DataTables.ColumnSettings { /** Set instance of Angular pipe to transform the data of particular column */ ngPipeInstance?: PipeTransform; + /** Set `TemplateRef` to transform the data of this column */ + ngTemplateRef?: ADTTemplateRef; +} + +export interface ADTTemplateRef { + /** `TemplateRef` to work with */ + ref: TemplateRef; + /** */ + context?: ADTTemplateRefContext; +} + +export interface ADTTemplateRefContext { + captureEvents: Function; + userData?: any; }