From fdb1daf32b3f9c80c7f391b093f96f4a3a39b030 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Fri, 8 Jul 2016 17:12:20 +0300 Subject: [PATCH] FIX: All events should execute inside zone --- nativescript-angular/renderer.ts | 28 +++++++++++------ tests/app/tests/renderer-tests.ts | 49 ++++++++++++++++++++++++++--- tests/app/tests/style-properties.ts | 4 +-- tests/app/tests/test-app.ts | 25 ++++++++------- tests/package.json | 2 +- 5 files changed, 79 insertions(+), 29 deletions(-) diff --git a/nativescript-angular/renderer.ts b/nativescript-angular/renderer.ts index 0326c74d4..d8b01ab76 100644 --- a/nativescript-angular/renderer.ts +++ b/nativescript-angular/renderer.ts @@ -1,4 +1,4 @@ -import {Inject, Injectable, Optional} from '@angular/core/src/di'; +import {Inject, Injectable, Optional, NgZone} from '@angular/core'; import { Renderer, RootRenderer, @@ -28,7 +28,9 @@ export class NativeScriptRootRenderer implements RootRenderer { constructor( @Optional() @Inject(APP_ROOT_VIEW) private _rootView: View, @Inject(DEVICE) device: Device, - private _animationDriver: AnimationDriver) { + private _animationDriver: AnimationDriver, + private _zone: NgZone) { + this._viewUtil = new ViewUtil(device); } @@ -52,7 +54,7 @@ export class NativeScriptRootRenderer implements RootRenderer { renderComponent(componentProto: RenderComponentType): Renderer { let renderer = this._registeredComponents.get(componentProto.id); if (isBlank(renderer)) { - renderer = new NativeScriptRenderer(this, componentProto, this._animationDriver); + renderer = new NativeScriptRenderer(this, componentProto, this._animationDriver, this._zone); this._registeredComponents.set(componentProto.id, renderer); } return renderer; @@ -63,16 +65,18 @@ export class NativeScriptRootRenderer implements RootRenderer { export class NativeScriptRenderer extends Renderer { private componentProtoId: string; private hasComponentStyles: boolean; - private rootRenderer: NativeScriptRootRenderer; private get viewUtil(): ViewUtil { return this.rootRenderer.viewUtil; } - constructor(private _rootRenderer: NativeScriptRootRenderer, private componentProto: RenderComponentType, private animationDriver: AnimationDriver) { + constructor( + private rootRenderer: NativeScriptRootRenderer, + private componentProto: RenderComponentType, + private animationDriver: AnimationDriver, + private zone: NgZone) { + super(); - this.rootRenderer = _rootRenderer; - let page = this.rootRenderer.page; let stylesLength = componentProto.styles.length; this.componentProtoId = componentProto.id; for (let i = 0; i < stylesLength; i++) { @@ -93,7 +97,7 @@ export class NativeScriptRenderer extends Renderer { } renderComponent(componentProto: RenderComponentType): Renderer { - return this._rootRenderer.renderComponent(componentProto); + return this.rootRenderer.renderComponent(componentProto); } selectRootElement(selector: string): NgView { @@ -211,7 +215,13 @@ export class NativeScriptRenderer extends Renderer { public listen(renderElement: NgView, eventName: string, callback: Function): Function { traceLog('NativeScriptRenderer.listen: ' + eventName); - let zonedCallback = (global).Zone.current.wrap(callback); + // Explicitly wrap in zone + let zonedCallback = (...args) => { + this.zone.run(() => { + callback.apply(undefined, args); + }); + }; + renderElement.on(eventName, zonedCallback); if (eventName === View.loadedEvent && renderElement.isLoaded) { const notifyData = { eventName: View.loadedEvent, object: renderElement }; diff --git a/tests/app/tests/renderer-tests.ts b/tests/app/tests/renderer-tests.ts index 7cee978e8..f1e8c0b30 100644 --- a/tests/app/tests/renderer-tests.ts +++ b/tests/app/tests/renderer-tests.ts @@ -1,6 +1,6 @@ //make sure you import mocha-config before @angular/core import {assert} from "./test-config"; -import {Component, ElementRef, Renderer} from "@angular/core"; +import {Component, ElementRef, Renderer, NgZone} from "@angular/core"; import {ProxyViewContainer} from "ui/proxy-view-container"; import {Red} from "color/known-colors"; import {dumpView} from "./test-utils"; @@ -109,7 +109,8 @@ describe('Renderer E2E', () => { before(() => { return TestApp.create().then((app) => { testApp = app; - }) + + }); }); after(() => { @@ -163,6 +164,44 @@ describe('Renderer E2E', () => { }); }); + it("executes events inside NgZone when listen is called inside NgZone", (done) => { + const eventName = "someEvent"; + const view = new StackLayout(); + const evetArg = { eventName, object: view }; + const callback = (arg) => { + assert.equal(arg, evetArg); + assert.isTrue(NgZone.isInAngularZone(), "Event should be executed inside NgZone"); + done(); + }; + + testApp.zone.run(() => { + testApp.renderer.listen(view, eventName, callback); + }); + + setTimeout(() => { + testApp.zone.runOutsideAngular(() => { + view.notify(evetArg); + }); + }, 10); + }); + + it("executes events inside NgZone when listen is called outside NgZone", (done) => { + const eventName = "someEvent"; + const view = new StackLayout(); + const evetArg = { eventName, object: view }; + const callback = (arg) => { + assert.equal(arg, evetArg); + assert.isTrue(NgZone.isInAngularZone(), "Event should be executed inside NgZone"); + done(); + }; + + testApp.zone.runOutsideAngular(() => { + testApp.renderer.listen(view, eventName, callback); + + view.notify(evetArg); + }); + }); + describe("Structural directives", () => { it("ngIf hides component when false", () => { return testApp.loadComponent(NgIfLabel).then((componentRef) => { @@ -180,7 +219,7 @@ describe('Renderer E2E', () => { testApp.appRef.tick(); assert.equal("(ProxyViewContainer (template), (Label))", dumpView(componentRoot)); }); - }) + }); it("ngFor creates element for each item", () => { return testApp.loadComponent(NgForLabel).then((componentRef) => { @@ -212,8 +251,8 @@ describe('Renderer E2E', () => { assert.equal("(ProxyViewContainer (template), (Label[text=one]), (Label[text=new]), (Label[text=two]), (Label[text=three]))", dumpView(componentRoot, true)); }); }); - }) -}) + }); +}); describe('Renderer createElement', () => { let testApp: TestApp = null; diff --git a/tests/app/tests/style-properties.ts b/tests/app/tests/style-properties.ts index 515a554d7..d4e3a89ab 100644 --- a/tests/app/tests/style-properties.ts +++ b/tests/app/tests/style-properties.ts @@ -14,10 +14,10 @@ describe("Setting style properties", () => { beforeEach(() => { const animationDriver = new NativeScriptAnimationDriver() - const rootRenderer = new NativeScriptRootRenderer(null, device, animationDriver); + const rootRenderer = new NativeScriptRootRenderer(null, device, animationDriver, null); const componentType = new RenderComponentType("id", "templateUrl", 0, null, []); - renderer = new NativeScriptRenderer(rootRenderer, componentType, animationDriver); + renderer = new NativeScriptRenderer(rootRenderer, componentType, animationDriver, null); element = new TextField(); }); diff --git a/tests/app/tests/test-app.ts b/tests/app/tests/test-app.ts index f5fdc631d..39071c625 100644 --- a/tests/app/tests/test-app.ts +++ b/tests/app/tests/test-app.ts @@ -1,10 +1,9 @@ //make sure you import mocha-config before @angular/core import {bootstrap, ProviderArray} from "nativescript-angular/application"; import {Type, Component, ComponentRef, DynamicComponentLoader, - ViewChild, ElementRef, provide, ApplicationRef, Renderer, ViewContainerRef + ViewChild, ElementRef, provide, ApplicationRef, Renderer, ViewContainerRef, NgZone } from "@angular/core"; -import {View} from "ui/core/view"; import {GridLayout} from "ui/layouts/grid-layout"; import {LayoutBase} from "ui/layouts/layout-base"; import {topmost} from 'ui/frame'; @@ -21,7 +20,8 @@ export class TestApp { constructor(public loader: DynamicComponentLoader, public elementRef: ViewContainerRef, public appRef: ApplicationRef, - public renderer: Renderer) { + public renderer: Renderer, + public zone: NgZone) { } public loadComponent(type: Type): Promise> { @@ -49,7 +49,7 @@ export class TestApp { } } -var runningApps = new Map(); +const runningApps = new Map(); export function bootstrapTestApp(appComponentType: new (...args) => T, providers: ProviderArray = []): Promise { const page = topmost().currentPage; @@ -61,17 +61,18 @@ export function bootstrapTestApp(appComponentType: new (...args) => T, provid viewRoot.opacity = 0.7; GridLayout.setRowSpan(rootLayout, 50); GridLayout.setColumnSpan(rootLayout, 50); - + const rootViewProvider = provide(APP_ROOT_VIEW, { useValue: viewRoot }); return bootstrap(appComponentType, providers.concat(rootViewProvider)).then((componentRef) => { - componentRef.injector.get(ApplicationRef) + componentRef.injector.get(ApplicationRef); const testApp = componentRef.instance; - - runningApps.set(testApp, { - hostView: rootLayout, - appRoot: viewRoot, - appRef: componentRef.injector.get(ApplicationRef) }); - + + runningApps.set(testApp, { + hostView: rootLayout, + appRoot: viewRoot, + appRef: componentRef.injector.get(ApplicationRef) + }); + return testApp; }); } diff --git a/tests/package.json b/tests/package.json index fcb587a6e..ff9888943 100644 --- a/tests/package.json +++ b/tests/package.json @@ -5,7 +5,7 @@ "version": "2.1.1" }, "tns-ios": { - "version": "2.0.1" + "version": "2.1.1" } }, "name": "ngtests",