Skip to content

Commit e74ac41

Browse files
authored
Fix dxi component template rerendering issues (#560)
* Introduce templated content in dxi components (T532675) * Fix interface naming inconsistency
1 parent a7290f6 commit e74ac41

File tree

8 files changed

+89
-25
lines changed

8 files changed

+89
-25
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,18 @@ export class AppComponent {
457457
}
458458
```
459459

460+
If your item template contains some *nested* components, declare it using the parameterless `dxTemplate` structural directive as follows:
461+
462+
```html
463+
<dx-list>
464+
<dxi-item>
465+
<div *dxTemplate>
466+
<dx-button text="I'm a nested child component"></dx-button>
467+
</div>
468+
</dxi-item>
469+
</dx-list>
470+
```
471+
460472
Angular has a built-in `template` directive. To define the `template` property of the configuration component (for example, `dxo-master-detail`), use the following code:
461473
```html
462474
<dxo-master-detail [template]="'masterDetail'"></dxo-master-detail>

src/core/component.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '@angular/core';
88

99
import { DxTemplateDirective } from './template';
10-
import { DxTemplateHost } from './template-host';
10+
import { IDxTemplateHost, DxTemplateHost } from './template-host';
1111
import { EmitterHelper } from './events-strategy';
1212
import { WatcherHelper } from './watcher-helper';
1313
import {
@@ -17,7 +17,8 @@ import {
1717
CollectionNestedOptionContainerImpl
1818
} from './nested-option';
1919

20-
export abstract class DxComponent implements AfterViewInit, AfterContentChecked, INestedOptionContainer, ICollectionNestedOptionContainer {
20+
export abstract class DxComponent implements AfterViewInit, AfterContentChecked,
21+
INestedOptionContainer, ICollectionNestedOptionContainer, IDxTemplateHost {
2122
private _optionToUpdate: any = {};
2223
private _collectionContainerImpl: ICollectionNestedOptionContainer;
2324
eventHelper: EmitterHelper;

src/core/events-strategy.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { DxComponent } from './component';
33

44
const dxToNgEventNames = {};
55

6-
interface EventSubscription {
6+
interface IEventSubscription {
77
handler: any;
88
unsubscribe: () => void;
99
}
1010

1111
export class NgEventsStrategy {
12-
private subscriptions: { [key: string]: EventSubscription[] } = {};
12+
private subscriptions: { [key: string]: IEventSubscription[] } = {};
1313

1414
constructor(private component: DxComponent, private ngZone: NgZone) { }
1515

@@ -62,13 +62,13 @@ export class NgEventsStrategy {
6262
}
6363
}
6464

65-
interface RememberedEvent {
65+
interface IRememberedEvent {
6666
name: string;
6767
context: EmitterHelper;
6868
}
6969

70-
let events: RememberedEvent[] = [];
71-
let onStableSubscription: EventSubscription = null;
70+
let events: IRememberedEvent[] = [];
71+
let onStableSubscription: IEventSubscription = null;
7272

7373
let createOnStableSubscription = function(ngZone: NgZone, fireNgEvent: Function) {
7474
if (onStableSubscription) {

src/core/nested-option.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ export interface INestedOptionContainer {
1111
instance: any;
1212
}
1313

14-
export interface OptionPathGetter { (): string; }
14+
export interface IOptionPathGetter { (): string; }
1515

1616
export abstract class BaseNestedOption implements INestedOptionContainer, ICollectionNestedOptionContainer {
1717
protected _host: INestedOptionContainer;
18-
protected _hostOptionPath: OptionPathGetter;
18+
protected _hostOptionPath: IOptionPathGetter;
1919
private _collectionContainerImpl: ICollectionNestedOptionContainer;
2020
protected _initialOptions = {};
2121

@@ -42,7 +42,7 @@ export abstract class BaseNestedOption implements INestedOptionContainer, IColle
4242
}
4343
}
4444

45-
setHost(host: INestedOptionContainer, optionPath: OptionPathGetter) {
45+
setHost(host: INestedOptionContainer, optionPath: IOptionPathGetter) {
4646
this._host = host;
4747
this._hostOptionPath = optionPath;
4848
}
@@ -91,7 +91,7 @@ export class CollectionNestedOptionContainerImpl implements ICollectionNestedOpt
9191
}
9292

9393
export abstract class NestedOption extends BaseNestedOption {
94-
setHost(host: INestedOptionContainer, optionPath: OptionPathGetter) {
94+
setHost(host: INestedOptionContainer, optionPath: IOptionPathGetter) {
9595
super.setHost(host, optionPath);
9696

9797
this._host[this._optionPath] = this._initialOptions;
@@ -123,10 +123,10 @@ export abstract class CollectionNestedOption extends BaseNestedOption implements
123123
}
124124
}
125125

126-
export interface OptionWithTemplate extends BaseNestedOption {
126+
export interface IOptionWithTemplate extends BaseNestedOption {
127127
template: any;
128128
}
129-
export function extractTemplate(option: OptionWithTemplate, element: ElementRef) {
129+
export function extractTemplate(option: IOptionWithTemplate, element: ElementRef) {
130130
if (!option.template === undefined || !element.nativeElement.hasChildNodes()) {
131131
return;
132132
}
@@ -184,9 +184,9 @@ export function extractTemplate(option: OptionWithTemplate, element: ElementRef)
184184

185185
export class NestedOptionHost {
186186
private _host: INestedOptionContainer;
187-
private _optionPath: OptionPathGetter;
187+
private _optionPath: IOptionPathGetter;
188188

189-
setHost(host: INestedOptionContainer, optionPath?: OptionPathGetter) {
189+
setHost(host: INestedOptionContainer, optionPath?: IOptionPathGetter) {
190190
this._host = host;
191191
this._optionPath = optionPath || (() => '');
192192
}

src/core/template-host.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { DxComponent } from './component';
21
import { DxTemplateDirective } from './template';
32

3+
export interface IDxTemplateHost {
4+
setTemplate(template: DxTemplateDirective);
5+
};
6+
47
export class DxTemplateHost {
5-
host: DxComponent;
6-
setHost(host: DxComponent) {
8+
host: IDxTemplateHost;
9+
setHost(host: IDxTemplateHost) {
710
this.host = host;
811
}
912
setTemplate(template: DxTemplateDirective) {

src/core/template.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class RenderData {
2323
}
2424

2525
@Directive({
26-
selector: '[dxTemplate][dxTemplateOf]'
26+
selector: '[dxTemplate]'
2727
})
2828
export class DxTemplateDirective {
2929
@Input()

templates/nested-component.tst

+14-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import {
1313
QueryList<#?#>
1414
} from '@angular/core';
1515

16-
import { NestedOptionHost<#? it.hasTemplate #>, extractTemplate<#?#> } from '../../core/nested-option';
16+
import { NestedOptionHost<#? it.hasTemplate #>, extractTemplate<#?#> } from '../../core/nested-option';<#? it.hasTemplate #>
17+
import { DxTemplateDirective } from '../../core/template';
18+
import { IDxTemplateHost, DxTemplateHost } from '../../core/template-host';<#?#>
1719
import { <#= it.baseClass #> } from '<#= it.basePath #>';
1820
<#~ it.collectionNestedComponents :component:i #><#? component.className !== it.className #>import { <#= component.className #>Component } from './<#= component.path #>';
1921
<#?#><#~#>
@@ -22,12 +24,12 @@ import { <#= it.baseClass #> } from '<#= it.basePath #>';
2224
selector: '<#= it.selector #>',
2325
template: '<#? it.hasTemplate #><ng-content></ng-content><#?#>',
2426
styles: ['<#? it.hasTemplate #>:host { display: block; }<#?#>'],
25-
providers: [NestedOptionHost]<#? it.inputs #>,
27+
providers: [NestedOptionHost<#? it.hasTemplate #>, DxTemplateHost<#?#>]<#? it.inputs #>,
2628
inputs: [<#~ it.inputs :input:i #>
2729
'<#= input.name #>'<#? i < it.inputs.length-1 #>,<#?#><#~#>
2830
]<#?#>
2931
})
30-
export class <#= it.className #>Component extends <#= it.baseClass #><#? it.hasTemplate #> implements AfterViewInit<#?#> {<#~ it.properties :prop:i #>
32+
export class <#= it.className #>Component extends <#= it.baseClass #><#? it.hasTemplate #> implements AfterViewInit, IDxTemplateHost<#?#> {<#~ it.properties :prop:i #>
3133
@Input()
3234
get <#= prop.name #>() {
3335
return this._getOption('<#= prop.name #>');
@@ -49,13 +51,19 @@ export class <#= it.className #>Component extends <#= it.baseClass #><#? it.hasT
4951
this.setChildren('<#= component.propertyName #>', value);
5052
}
5153
<#~#>
52-
53-
constructor(@SkipSelf() @Host() parentOptionHost: NestedOptionHost, @Host() optionHost: NestedOptionHost<#? it.hasTemplate #>, private element: ElementRef<#?#>) {
54+
constructor(@SkipSelf() @Host() parentOptionHost: NestedOptionHost,
55+
@Host() optionHost: NestedOptionHost<#? it.hasTemplate #>,
56+
@Host() templateHost: DxTemplateHost,
57+
private element: ElementRef<#?#>) {
5458
super();
5559
parentOptionHost.setNestedOption(this);
56-
optionHost.setHost(this, this._fullOptionPath.bind(this));
60+
optionHost.setHost(this, this._fullOptionPath.bind(this));<#? it.hasTemplate #>
61+
templateHost.setHost(this);<#?#>
5762
}
5863
<#? it.hasTemplate #>
64+
setTemplate(template: DxTemplateDirective) {
65+
this.template = template;
66+
}
5967
ngAfterViewInit() {
6068
extractTemplate(this, this.element);
6169
}

tests/src/ui/list.spec.ts

+40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* tslint:disable:component-selector */
22

33
import {
4+
VERSION,
45
Component,
56
ViewChildren,
67
QueryList,
@@ -515,4 +516,43 @@ describe('DxList', () => {
515516

516517
expect(fixture.componentInstance.buttonDestroyed).toBe(true);
517518
});
519+
520+
it('should use item template to render/rerender an item with a template (T532675)', async(() => {
521+
const ngTemplateName = Number(VERSION.major) >= 4 ? 'ng-template' : 'template';
522+
523+
TestBed.configureTestingModule({
524+
declarations: [TestContainerComponent],
525+
imports: [DxButtonModule, DxListModule]
526+
});
527+
528+
TestBed.overrideComponent(TestContainerComponent, {
529+
set: {
530+
template: `
531+
<dx-list>
532+
<dxi-item>
533+
<dx-button *dxTemplate></dx-button>
534+
</dxi-item>
535+
<dxi-item>
536+
<${ngTemplateName} dxTemplate>
537+
<dx-button></dx-button>
538+
</${ngTemplateName}>
539+
</dxi-item>
540+
</dx-list>
541+
`
542+
}
543+
});
544+
545+
let fixture = TestBed.createComponent(TestContainerComponent);
546+
fixture.detectChanges();
547+
548+
let instance = getWidget(fixture);
549+
expect(instance.element().find('.dx-button').eq(0).dxButton('instance')).not.toBeUndefined();
550+
expect(instance.element().find('.dx-button').eq(1).dxButton('instance')).not.toBeUndefined();
551+
552+
instance.repaint();
553+
fixture.detectChanges();
554+
expect(instance.element().find('.dx-button').eq(0).dxButton('instance')).not.toBeUndefined();
555+
expect(instance.element().find('.dx-button').eq(1).dxButton('instance')).not.toBeUndefined();
556+
}));
557+
518558
});

0 commit comments

Comments
 (0)