Skip to content

Commit

Permalink
Create event emitters fo nested components (#728)
Browse files Browse the repository at this point in the history
  • Loading branch information
anna-zhernovkova committed Mar 29, 2018
1 parent e218a59 commit a36fc32
Show file tree
Hide file tree
Showing 8 changed files with 614 additions and 92 deletions.
443 changes: 382 additions & 61 deletions metadata/NGMetaData.json

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions src/core/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,7 @@ export abstract class DxComponent implements OnChanges, OnInit, DoCheck, AfterCo
}

protected _createEventEmitters(events) {
events.forEach(event => {
this.eventHelper.createEmitter(event.emit, event.subscribe);
});
this.eventHelper.createEventEmitters(events);
}
_shouldOptionChange(name: string, value: any) {
if (this.changedOptions.hasOwnProperty(name)) {
Expand Down
10 changes: 8 additions & 2 deletions src/core/events-strategy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventEmitter, NgZone, Injectable } from '@angular/core';
import { DxComponent } from './component';
import * as eventsEngine from 'devextreme/events/core/events_engine';
import { BaseNestedOption } from './nested-option';

const dxToNgEventNames = {};

Expand All @@ -12,7 +13,7 @@ interface IEventSubscription {
export class NgEventsStrategy {
private subscriptions: { [key: string]: IEventSubscription[] } = {};

constructor(private component: DxComponent, private ngZone: NgZone) { }
constructor(private component: DxComponent | BaseNestedOption, private ngZone: NgZone) { }

hasEvent(name: string) {
return this.ngZone.run(() => {
Expand Down Expand Up @@ -69,7 +70,7 @@ export class EmitterHelper {
strategy: NgEventsStrategy;
lockedValueChangeEvent = false;

constructor(ngZone: NgZone, public component: DxComponent) {
constructor(ngZone: NgZone, public component: DxComponent | BaseNestedOption) {
this.strategy = new NgEventsStrategy(component, ngZone);
}
fireNgEvent(eventName: string, eventArgs: any) {
Expand All @@ -87,6 +88,11 @@ export class EmitterHelper {
dxToNgEventNames[dxEventName] = ngEventName;
}
}
createEventEmitters(events: Array<any>) {
events.forEach(event => {
this.createEmitter(event.emit, event.subscribe);
});
}
}

@Injectable()
Expand Down
53 changes: 50 additions & 3 deletions src/core/nested-option.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { QueryList, ElementRef, Renderer2 } from '@angular/core';
import { QueryList, ElementRef, Renderer2, NgZone, AfterContentInit } from '@angular/core';
import { ɵgetDOM as getDOM } from '@angular/platform-browser';

import { DX_TEMPLATE_WRAPPER_CLASS } from './template';
import { getElement } from './utils';
import { EmitterHelper } from './events-strategy';

import * as events from 'devextreme/events';

Expand All @@ -15,17 +16,27 @@ export interface INestedOptionContainer {

export interface IOptionPathGetter { (): string; }

export abstract class BaseNestedOption implements INestedOptionContainer, ICollectionNestedOptionContainer {
export abstract class BaseNestedOption implements INestedOptionContainer, ICollectionNestedOptionContainer, AfterContentInit {
protected _host: INestedOptionContainer;
protected _hostOptionPath: IOptionPathGetter;
private _collectionContainerImpl: ICollectionNestedOptionContainer;
protected _initialOptions = {};
eventHelper: EmitterHelper;

protected abstract get _optionPath(): string;
protected abstract _fullOptionPath(): string;
protected abstract _initEvents(): void;

constructor() {
constructor(ngZone: NgZone) {
this._collectionContainerImpl = new CollectionNestedOptionContainerImpl(this._setOption.bind(this), this._filterItems.bind(this));
this.eventHelper = new EmitterHelper(ngZone, this);
}

ngAfterContentInit() {
this._initEvents();
}
protected _createEventEmitters(events) {
this.eventHelper.createEventEmitters(events);
}

protected _getOption(name: string): any {
Expand Down Expand Up @@ -102,6 +113,19 @@ export abstract class NestedOption extends BaseNestedOption {
protected _fullOptionPath() {
return this._hostOptionPath() + this._optionPath + '.';
}

protected _initEvents() {
this.instance.on('optionChanged', (e) => {
let nameParts = e.fullName.split('.');

for (let i = 0; i <= nameParts.length - 2; i++ ) {
if (nameParts[i] === this._optionPath) {
this.eventHelper.fireNgEvent(nameParts[i + 1] + 'Change', [e.value]);
return;
}
};
});
}
}

export interface ICollectionNestedOption {
Expand All @@ -123,6 +147,29 @@ export abstract class CollectionNestedOption extends BaseNestedOption implements
get isLinked() {
return this._index !== undefined && !!this.instance;
}

protected _initEvents() {
if (!this.instance) {
return;
}

this.instance.on('optionChanged', (e) => {
let nameParts = e.fullName.split('.');

for (let i = 0; i <= nameParts.length - 2; i++ ) {
if (nameParts[i].indexOf('[') !== -1) {
let parts = nameParts[i].split('['),
name = parts[0],
index = parts[1].split(']')[0];

if (name === this._optionPath && +index === this._index) {
this.eventHelper.fireNgEvent(nameParts[i + 1] + 'Change', [e.value]);
return;
}
}
};
});
}
}

export interface IOptionWithTemplate extends BaseNestedOption {
Expand Down
21 changes: 18 additions & 3 deletions templates/nested-component.tst
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
import {
Component,
NgModule,
NgZone,
Host,<#? it.hasTemplate #>
ElementRef,
Renderer2,
Inject,
AfterViewInit,<#?#>
SkipSelf<#? it.properties #>,
Input<#?#><#? it.collectionNestedComponents.length #>,
Input<#?#><#? it.events #>,
Output,
EventEmitter<#?#><#? it.collectionNestedComponents.length #>,
ContentChildren,
forwardRef,
QueryList<#?#>
Expand Down Expand Up @@ -47,6 +50,12 @@ export class <#= it.className #>Component extends <#= it.baseClass #><#? it.hasT
this._setOption('<#= prop.name #>', value);
}
<#~#>
<#~ it.events :event:i #><#? event.description #>
/**
* <#= event.description #>
*/<#?#>
@Output() <#= event.emit #>: <#= event.type #>;<#? i < it.events.length-1 #>
<#?#><#~#>
protected get _optionPath() {
return '<#= it.optionName #>';
}
Expand All @@ -60,13 +69,19 @@ export class <#= it.className #>Component extends <#= it.baseClass #><#? it.hasT
this.setChildren('<#= component.propertyName #>', value);
}
<#~#>
constructor(@SkipSelf() @Host() parentOptionHost: NestedOptionHost,
constructor(ngZone: NgZone, @SkipSelf() @Host() parentOptionHost: NestedOptionHost,
@Host() optionHost: NestedOptionHost<#? it.hasTemplate #>,
private renderer: Renderer2,
@Inject(DOCUMENT) private document: any,
@Host() templateHost: DxTemplateHost,
private element: ElementRef<#?#>) {
super();
super(ngZone);<#? it.events #>

this._createEventEmitters([
<#~ it.events :event:i #>{ emit: '<#= event.emit #>' }<#? i < it.events.length-1 #>,
<#?#><#~#>
]);
<#?#>
parentOptionHost.setNestedOption(this);
optionHost.setHost(this, this._fullOptionPath.bind(this));<#? it.hasTemplate #>
templateHost.setHost(this);<#?#><#? it.optionName === 'dataSource' #>
Expand Down
77 changes: 71 additions & 6 deletions tests/src/core/nested-option.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,18 @@ export class DxoTestOptionComponent extends NestedOption {
this._setOption('testNestedOption', value);
}

@Output() testNestedOptionChange: EventEmitter<any>;

protected get _optionPath() {
return 'testOption';
}

constructor(@SkipSelf() @Host() private _pnoh: NestedOptionHost, @Host() private _noh: NestedOptionHost) {
super();
constructor(ngZone: NgZone, @SkipSelf() @Host() private _pnoh: NestedOptionHost, @Host() private _noh: NestedOptionHost) {
super(ngZone);

this._createEventEmitters([
{ emit: 'testNestedOptionChange' }
]);

this._pnoh.setNestedOption(this);
this._noh.setHost(this);
Expand All @@ -93,12 +99,18 @@ export class DxiTestCollectionOptionComponent extends CollectionNestedOption {
this._setOption('testOption', value);
}

@Output() testOptionChange: EventEmitter<any>;

protected get _optionPath() {
return 'testCollectionOption';
}

constructor(@SkipSelf() @Host() private _pnoh: NestedOptionHost, @Host() private _noh: NestedOptionHost) {
super();
constructor(ngZone: NgZone, @SkipSelf() @Host() private _pnoh: NestedOptionHost, @Host() private _noh: NestedOptionHost) {
super(ngZone);

this._createEventEmitters([
{ emit: 'testOptionChange' }
]);

this._pnoh.setNestedOption(this);
this._noh.setHost(this, this._fullOptionPath.bind(this));
Expand All @@ -124,12 +136,13 @@ export class DxiTestCollectionOptionWithTemplateComponent extends CollectionNest

shownEventFired = false;

constructor(@SkipSelf() @Host() private _pnoh: NestedOptionHost,
constructor(ngZone: NgZone,
@SkipSelf() @Host() private _pnoh: NestedOptionHost,
@Host() private _noh: NestedOptionHost,
private element: ElementRef,
private renderer: Renderer2,
@Inject(DOCUMENT) private document: any) {
super();
super(ngZone);

this._pnoh.setNestedOption(this);
this._noh.setHost(this, this._fullOptionPath.bind(this));
Expand Down Expand Up @@ -230,6 +243,8 @@ export class DxTestWidgetComponent extends DxComponent {
export class TestContainerComponent {
testOption: string;
@ViewChildren(DxTestWidgetComponent) innerWidgets: QueryList<DxTestWidgetComponent>;

testMethod() {}
}


Expand Down Expand Up @@ -314,6 +329,56 @@ describe('DevExtreme Angular widget', () => {
expect(instance.option('testCollectionOption')[0].testOption).toEqual({ testNestedOption: 'text' });
}));

it('nested option should emit change event', async(() => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
template: `
<dx-test-widget>
<dxo-test-option
[(testNestedOption)]='testOption'
(testNestedOptionChange)='testMethod()'>
</dxo-test-option>
</dx-test-widget>
`
}
});
let fixture = TestBed.createComponent(TestContainerComponent);
fixture.detectChanges();

let testComponent = fixture.componentInstance,
instance = getWidget(fixture),
testSpy = spyOn(testComponent, 'testMethod');

instance.option('testOption.testNestedOption', 'new value');
fixture.detectChanges();
expect(testSpy).toHaveBeenCalledTimes(1);
}));

it('collection nested option should emit change event', async(() => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
template: `
<dx-test-widget>
<dxi-test-collection-option
[(testOption)]='testOption'
(testOptionChange)='testMethod()'>
</dxi-test-collection-option>
</dx-test-widget>
`
}
});
let fixture = TestBed.createComponent(TestContainerComponent);
fixture.detectChanges();

let testComponent = fixture.componentInstance,
instance = getWidget(fixture),
testSpy = spyOn(testComponent, 'testMethod');

instance.option('testCollectionOption[0].testOption', 'new value');
fixture.detectChanges();
expect(testSpy).toHaveBeenCalledTimes(1);
}));

it('method template.render of nested option should trigger shownEvent after rendering', async(() => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
Expand Down
Loading

0 comments on commit a36fc32

Please sign in to comment.