Skip to content

Commit ccb45bc

Browse files
Fix Template dynamic name change (#1314)
* Fix Template dynamic name change * add missing semicolon * Fix old tests * fix lint errors * FIx old memory leak * Added test * simplify test * optimization + rename + test * fix lint errors
1 parent d7ac05b commit ccb45bc

File tree

6 files changed

+158
-23
lines changed

6 files changed

+158
-23
lines changed

packages/devextreme-angular/src/core/component.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ import {
3434

3535
let serverStateKey;
3636
export const getServerStateKey = () => {
37-
if (!serverStateKey) {
38-
serverStateKey = makeStateKey<any>('DX_isPlatformServer');
39-
}
37+
if (!serverStateKey) {
38+
serverStateKey = makeStateKey<any>('DX_isPlatformServer');
39+
}
4040

41-
return serverStateKey;
41+
return serverStateKey;
4242
};
4343

4444
@Component({
@@ -58,14 +58,17 @@ export abstract class DxComponent implements OnChanges, OnInit, DoCheck, AfterCo
5858
removedNestedComponents = [];
5959
recreatedNestedComponents: any[];
6060
widgetUpdateLocked = false;
61+
templateUpdateRequired = false;
6162

62-
private _initTemplates() {
63-
if (this.templates.length) {
64-
let initialTemplates = {};
63+
private _updateTemplates() {
64+
if (this.templates.length && this.templateUpdateRequired) {
65+
let updatedTemplates = {};
6566
this.templates.forEach(template => {
66-
initialTemplates[template.name] = template;
67+
updatedTemplates[template.name] = template;
6768
});
68-
this.instance.option('integrationOptions.templates', initialTemplates);
69+
this.instance.option('integrationOptions.templates', updatedTemplates);
70+
this.templates = Object.values(updatedTemplates);
71+
this.templateUpdateRequired = false;
6972
}
7073
}
7174

@@ -105,7 +108,7 @@ export abstract class DxComponent implements OnChanges, OnInit, DoCheck, AfterCo
105108
return strategy;
106109
};
107110

108-
this._initialOptions.nestedComponentOptions = function(component) {
111+
this._initialOptions.nestedComponentOptions = function (component) {
109112
return {
110113
eventsStrategy: (instance) => { return new NgEventsStrategy(instance, zone); },
111114
nestedComponentOptions: component.option('nestedComponentOptions')
@@ -216,13 +219,14 @@ export abstract class DxComponent implements OnChanges, OnInit, DoCheck, AfterCo
216219
}
217220

218221
ngAfterContentChecked() {
222+
this._updateTemplates();
219223
this.applyOptions();
220224
this.resetOptions();
221225
this.unlockWidgetUpdate();
222226
}
223227

224228
ngAfterViewInit() {
225-
this._initTemplates();
229+
this._updateTemplates();
226230
this.instance.endUpdate();
227231
this.recreatedNestedComponents = [];
228232
}
@@ -241,9 +245,9 @@ export abstract class DxComponent implements OnChanges, OnInit, DoCheck, AfterCo
241245
this.removedNestedComponents.filter(option => option &&
242246
!this.isRecreated(option) &&
243247
collectionName ? option.startsWith(collectionName) : true)
244-
.forEach(option => {
245-
this.instance.resetOption(option);
246-
});
248+
.forEach(option => {
249+
this.instance.resetOption(option);
250+
});
247251

248252
this.removedNestedComponents = [];
249253
this.recreatedNestedComponents = [];
@@ -252,11 +256,12 @@ export abstract class DxComponent implements OnChanges, OnInit, DoCheck, AfterCo
252256

253257
isRecreated(name: string): boolean {
254258
return this.recreatedNestedComponents &&
255-
this.recreatedNestedComponents.some(nestedComponent => nestedComponent.getOptionPath() === name);
259+
this.recreatedNestedComponents.some(nestedComponent => nestedComponent.getOptionPath() === name);
256260
}
257261

258262
setTemplate(template: DxTemplateDirective) {
259263
this.templates.push(template);
264+
this.templateUpdateRequired = true;
260265
}
261266

262267
setChildren<T extends ICollectionNestedOption>(propertyName: string, items: QueryList<T>) {

packages/devextreme-angular/tests/src/core/template.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,19 @@ export class TestContainerComponent {
122122

123123
@Output() onInnerElementClicked = new EventEmitter<any>();
124124

125+
dynamicTemplateName: string;
126+
127+
constructor() {
128+
this.dynamicTemplateName = 'start';
129+
}
130+
125131
testFunction() {
126132
this.onInnerElementClicked.next();
127133
}
134+
135+
switchTemplateName() {
136+
this.dynamicTemplateName = this.dynamicTemplateName === 'start' ? 'end' : 'start';
137+
}
128138
}
129139

130140

@@ -228,5 +238,44 @@ describe('DevExtreme Angular widget\'s template', () => {
228238

229239
fixture.nativeElement.querySelector('.elem').click();
230240
});
241+
242+
it('should work with dynamic template name', () => {
243+
TestBed.overrideComponent(TestContainerComponent, {
244+
set: {
245+
template: `
246+
<dx-test-widget [testTemplate]="dynamicTemplateName">
247+
<div *dxTemplate="let d of dynamicTemplateName">
248+
<div [class]="dynamicTemplateName">
249+
Template content: {{dynamicTemplateName}}
250+
</div>
251+
</div>
252+
</dx-test-widget>
253+
`}
254+
});
255+
let fixture = TestBed.createComponent(TestContainerComponent);
256+
fixture.detectChanges();
257+
258+
let testComponent = fixture.componentInstance,
259+
innerComponent = testComponent.widget,
260+
template = innerComponent.testTemplate,
261+
templatesHash = innerComponent.instance.option('integrationOptions.templates'),
262+
container = document.createElement('div');
263+
264+
expect(template).not.toBeUndefined;
265+
266+
templatesHash[template].render({ container: container });
267+
fixture.detectChanges();
268+
269+
expect(container.querySelector('.start')).not.toBeNull();
270+
271+
testComponent.switchTemplateName();
272+
fixture.detectChanges();
273+
expect(template).not.toBeUndefined;
274+
275+
templatesHash[template].render({ container: container });
276+
fixture.detectChanges();
277+
278+
expect(container.querySelector('.end')).not.toBeNull();
279+
});
231280
});
232281

packages/devextreme-angular/tests/src/server/ssr-is-platform-server.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
template: ''
2626
})
2727
class TestContainerComponent {
28-
renderedOnServer: false;
28+
renderedOnServer = false;
2929
initializedHandler(e) {
3030
this.renderedOnServer = e.component.option('integrationOptions.renderedOnServer');
3131
}

packages/devextreme-angular/tests/src/ui/chart.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import {
1313
DxChartModule, DxChartComponent, DxScrollViewModule
1414
} from 'devextreme-angular';
15+
import dxChart from 'devextreme/viz/chart';
1516

1617
@Component({
1718
selector: 'test-container-component',
@@ -79,7 +80,7 @@ describe('DxChart', () => {
7980

8081
let testComponent = fixture.componentInstance,
8182
chart = testComponent.chart,
82-
spy = spyOn(chart.instance, '_applyChanges');
83+
spy = spyOn<dxChart & {_applyChanges?: any}>(chart.instance, '_applyChanges');
8384

8485
testComponent.dataSource = [{
8586
name: 1,
@@ -109,7 +110,7 @@ describe('DxChart', () => {
109110

110111
let testComponent = fixture.componentInstance,
111112
chart = testComponent.chart,
112-
spy = spyOn(chart.instance, '_applyChanges');
113+
spy = spyOn<dxChart & {_applyChanges?: any}>(chart.instance, '_applyChanges');
113114

114115
testComponent.dataSource.push({
115116
name: 1,

packages/devextreme-angular/tests/src/ui/data-grid.spec.ts

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import {
44
Component,
55
ViewChildren,
6-
QueryList
6+
QueryList,
7+
ViewChild
78
} from '@angular/core';
89

910
import {
@@ -40,17 +41,17 @@ class TestContainerComponent {
4041
{ dataField: 'number' }
4142
];
4243

43-
dataSourceWithUndefined = [{ obj: { field: undefined }}];
44+
dataSourceWithUndefined = [{ obj: { field: undefined } }];
4445

4546
columsChanged = 0;
4647
@ViewChildren(DxDataGridComponent) innerWidgets: QueryList<DxDataGridComponent>;
4748

48-
testMethod() {}
49+
testMethod() { }
4950

5051
getCellValue() {
5152
return {};
5253
}
53-
onRowPrepared() {}
54+
onRowPrepared() { }
5455

5556
onOptionChanged(e) {
5657
if (e.name === 'columns') {
@@ -313,6 +314,83 @@ describe('DxDataGrid', () => {
313314
expect(fixture.componentInstance.isDestroyed).toBe(true);
314315
jasmine.clock().uninstall();
315316
});
317+
318+
// https://supportcenter.devexpress.com/internal/ticket/details/T1124163
319+
it('should update template with dynamic name', () => {
320+
const columnsA = [
321+
{ field: 'SomeFieldA', caption: 'FieldA', cellTemplateName: 'templateA' },
322+
{ field: 'otherField', caption: 'Other field1', cellTemplateName: undefined }
323+
];
324+
325+
const columnsB = [
326+
{ field: 'SomeFieldB', caption: 'FieldB', cellTemplateName: 'templateB' },
327+
{ field: 'otherField', caption: 'Other field2', cellTemplateName: undefined }
328+
];
329+
@Component({
330+
selector: 'test-container-component',
331+
template: ''
332+
})
333+
class TestGridComponent {
334+
@ViewChild('#gridContainer') grid: DxDataGridComponent;
335+
dataSource: any[];
336+
columns: any[];
337+
338+
constructor() {
339+
this.dataSource = [{ ID: 0, SomeFieldA: 'a0', otherField: 'b0' }];
340+
this.columns = columnsA;
341+
}
342+
toggleData() {
343+
if (this.columns[0].field === 'SomeFieldA') {
344+
this.columns = columnsB;
345+
} else {
346+
this.columns = columnsA;
347+
}
348+
}
349+
}
350+
351+
TestBed.configureTestingModule({
352+
declarations: [TestGridComponent],
353+
imports: [DxDataGridModule]
354+
});
355+
356+
TestBed.overrideComponent(TestGridComponent, {
357+
set: {
358+
template: `
359+
<dx-data-grid id="gridContainer" [dataSource]="dataSource" [repaintChangesOnly]="true">
360+
<dxo-sorting mode="none"></dxo-sorting>
361+
362+
<ng-container *ngFor="let col of columns">
363+
<dxi-column [caption]="col.caption" [dataField]="col.field" [cellTemplate]="col.cellTemplateName">
364+
</dxi-column>
365+
366+
<div *dxTemplate="let cell of col.cellTemplateName">
367+
<div *ngIf="col.cellTemplateName === 'templateA'">
368+
someTemplateA
369+
</div>
370+
<div *ngIf="col.cellTemplateName === 'templateB'">
371+
<div class="templBClass">someTemplateB</div>
372+
</div>
373+
</div>
374+
375+
</ng-container>
376+
</dx-data-grid>`
377+
}
378+
});
379+
380+
jasmine.clock().uninstall();
381+
jasmine.clock().install();
382+
383+
let fixture = TestBed.createComponent(TestGridComponent);
384+
fixture.detectChanges();
385+
386+
const component = fixture.componentInstance;
387+
component.toggleData();
388+
fixture.detectChanges();
389+
390+
jasmine.clock().tick(101);
391+
expect(fixture.nativeElement.querySelector('.templBClass')).toBeTruthy();
392+
jasmine.clock().uninstall();
393+
});
316394
});
317395

318396
describe('Nested DxDataGrid', () => {
@@ -329,7 +407,7 @@ describe('Nested DxDataGrid', () => {
329407
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
330408
});
331409

332-
afterEach(function() {
410+
afterEach(function () {
333411
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
334412
});
335413

packages/devextreme-angular/tests/src/ui/select-box.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@ describe('DxSelectBox', () => {
7878
let instance = selectBox.instance;
7979

8080
instance.option('value', 2);
81+
// @ts-ignore
8182
expect(instance.option('text')).toBe(1);
8283

8384
instance.option('value', 2);
85+
// @ts-ignore
8486
expect(instance.option('text')).toBe(1);
8587
});
8688
});

0 commit comments

Comments
 (0)