Fix some angular (#16615)

* fix(angular): platform types

fixes #16535

* fix(angular): memory leak in lifecycle events

fixes #16285

* fix ci

* single core
This commit is contained in:
Manu MA
2018-12-06 22:19:35 +01:00
committed by GitHub
parent 119e0c1fd2
commit edf3659949
4 changed files with 44 additions and 28 deletions

View File

@ -60,12 +60,9 @@ jobs:
working_directory: /tmp/workspace/core working_directory: /tmp/workspace/core
- save_cache: *save-cache-core - save_cache: *save-cache-core
- run: - run:
command: npm run build # --max-workers 1 --debug command: npm run build -- --max-workers 1
working_directory: /tmp/workspace/core working_directory: /tmp/workspace/core
- save_cache: *save-cache-core-stencil - save_cache: *save-cache-core-stencil
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- persist_to_workspace: - persist_to_workspace:
root: /tmp/workspace root: /tmp/workspace
paths: paths:
@ -80,6 +77,9 @@ jobs:
- run: - run:
command: npm install command: npm install
working_directory: /tmp/workspace/angular working_directory: /tmp/workspace/angular
- run:
command: sudo npm link
working_directory: /tmp/workspace/core
- run: - run:
command: sudo npm link @ionic/core command: sudo npm link @ionic/core
working_directory: /tmp/workspace/angular working_directory: /tmp/workspace/angular

View File

@ -1,5 +1,5 @@
import { ApplicationRef, ComponentFactoryResolver, Injectable, InjectionToken, Injector, NgZone, ViewContainerRef } from '@angular/core'; import { ApplicationRef, ComponentFactoryResolver, Injectable, InjectionToken, Injector, NgZone, ViewContainerRef } from '@angular/core';
import { FrameworkDelegate, ViewLifecycle } from '@ionic/core'; import { FrameworkDelegate } from '@ionic/core';
import { NavParams } from '../directives/navigation/nav-params'; import { NavParams } from '../directives/navigation/nav-params';
@ -24,6 +24,7 @@ export class AngularDelegate {
export class AngularFrameworkDelegate implements FrameworkDelegate { export class AngularFrameworkDelegate implements FrameworkDelegate {
private elRefMap = new WeakMap<HTMLElement, any>(); private elRefMap = new WeakMap<HTMLElement, any>();
private elEventsMap = new WeakMap<HTMLElement, () => void>();
constructor( constructor(
private resolver: ComponentFactoryResolver, private resolver: ComponentFactoryResolver,
@ -37,7 +38,8 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
return new Promise(resolve => { return new Promise(resolve => {
this.zone.run(() => { this.zone.run(() => {
const el = attachView( const el = attachView(
this.resolver, this.injector, this.location, this.appRef, this.elRefMap, this.resolver, this.injector, this.location, this.appRef,
this.elRefMap, this.elEventsMap,
container, component, params, cssClasses container, component, params, cssClasses
); );
resolve(el); resolve(el);
@ -52,6 +54,11 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
if (componentRef) { if (componentRef) {
componentRef.destroy(); componentRef.destroy();
this.elRefMap.delete(component); this.elRefMap.delete(component);
const unbindEvents = this.elEventsMap.get(component);
if (unbindEvents) {
unbindEvents();
this.elEventsMap.delete(component);
}
} }
resolve(); resolve();
}); });
@ -65,6 +72,7 @@ export function attachView(
location: ViewContainerRef | undefined, location: ViewContainerRef | undefined,
appRef: ApplicationRef, appRef: ApplicationRef,
elRefMap: WeakMap<HTMLElement, any>, elRefMap: WeakMap<HTMLElement, any>,
elEventsMap: WeakMap<HTMLElement, () => void>,
container: any, component: any, params: any, cssClasses: string[] | undefined container: any, component: any, params: any, cssClasses: string[] | undefined
) { ) {
const factory = resolver.resolveComponentFactory(component); const factory = resolver.resolveComponentFactory(component);
@ -86,34 +94,41 @@ export function attachView(
hostElement.classList.add(clazz); hostElement.classList.add(clazz);
} }
} }
bindLifecycleEvents(instance, hostElement); const unbindEvents = bindLifecycleEvents(instance, hostElement);
container.appendChild(hostElement); container.appendChild(hostElement);
if (!location) { if (!location) {
appRef.attachView(componentRef.hostView); appRef.attachView(componentRef.hostView);
} }
componentRef.changeDetectorRef.reattach(); componentRef.changeDetectorRef.reattach();
elRefMap.set(hostElement, componentRef); elRefMap.set(hostElement, componentRef);
elEventsMap.set(hostElement, unbindEvents);
return hostElement; return hostElement;
} }
const LIFECYCLES = [ const LIFECYCLES = [
ViewLifecycle.WillEnter, 'ionViewWillEnter',
ViewLifecycle.DidEnter, 'ionViewDidEnter',
ViewLifecycle.WillLeave, 'ionViewWillLeave',
ViewLifecycle.DidLeave, 'ionViewDidLeave',
ViewLifecycle.WillUnload 'ionViewWillUnload'
]; ];
export function bindLifecycleEvents(instance: any, element: HTMLElement) { export function bindLifecycleEvents(instance: any, element: HTMLElement) {
LIFECYCLES.forEach(eventName => { const unregisters = LIFECYCLES.map(eventName => {
element.addEventListener(eventName, (ev: any) => { const handler = (ev: any) => {
if (typeof instance[eventName] === 'function') { if (typeof instance[eventName] === 'function') {
instance[eventName](ev.detail); instance[eventName](ev.detail);
} }
}); };
element.addEventListener(eventName, handler);
return () => {
element.removeEventListener(eventName, handler);
};
}); });
return () => {
unregisters.forEach(fn => fn());
};
} }
const NavParamsToken = new InjectionToken<any>('NavParamsToken'); const NavParamsToken = new InjectionToken<any>('NavParamsToken');

View File

@ -1,10 +1,11 @@
import { EventEmitter, Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BackButtonDetail, Platforms, getPlatforms, isPlatform } from '@ionic/core'; import { BackButtonDetail, Platforms, getPlatforms, isPlatform } from '@ionic/core';
import { proxyEvent } from '../util/util'; import { proxyEvent } from '../util/util';
import { Subject, Subscription } from 'rxjs';
export interface BackButtonEmitter extends EventEmitter<BackButtonDetail> { export interface BackButtonEmitter extends Subject<BackButtonDetail> {
subscribeWithPriority(priority: number, callback: () => Promise<any> | void): void; subscribeWithPriority(priority: number, callback: () => Promise<any> | void): Subscription;
} }
@Injectable() @Injectable()
@ -15,7 +16,7 @@ export class Platform {
/** /**
* @hidden * @hidden
*/ */
backButton: BackButtonEmitter = new EventEmitter<BackButtonDetail>() as any; backButton: BackButtonEmitter = new Subject<BackButtonDetail>() as any;
/** /**
* The pause event emits when the native platform puts the application * The pause event emits when the native platform puts the application
@ -23,25 +24,25 @@ export class Platform {
* application. This event would emit when a Cordova app is put into * application. This event would emit when a Cordova app is put into
* the background, however, it would not fire on a standard web browser. * the background, however, it would not fire on a standard web browser.
*/ */
pause = new EventEmitter<void>(); pause = new Subject<void>();
/** /**
* The resume event emits when the native platform pulls the application * The resume event emits when the native platform pulls the application
* out from the background. This event would emit when a Cordova app comes * out from the background. This event would emit when a Cordova app comes
* out from the background, however, it would not fire on a standard web browser. * out from the background, however, it would not fire on a standard web browser.
*/ */
resume = new EventEmitter<void>(); resume = new Subject<void>();
/** /**
* The resize event emits when the browser window has changed dimensions. This * The resize event emits when the browser window has changed dimensions. This
* could be from a browser window being physically resized, or from a device * could be from a browser window being physically resized, or from a device
* changing orientation. * changing orientation.
*/ */
resize = new EventEmitter<void>(); resize = new Subject<void>();
constructor() { constructor() {
this.backButton.subscribeWithPriority = function(priority, callback) { this.backButton.subscribeWithPriority = function(priority, callback) {
return this.subscribe((ev: BackButtonDetail) => { return this.subscribe(ev => {
ev.register(priority, callback); ev.register(priority, callback);
}); });
}; };

View File

@ -1,4 +1,5 @@
import { ElementRef, EventEmitter } from '@angular/core'; import { ElementRef } from '@angular/core';
import { Subject } from 'rxjs';
export function inputs(instance: any, el: ElementRef, props: string[]) { export function inputs(instance: any, el: ElementRef, props: string[]) {
props.forEach(propName => { props.forEach(propName => {
@ -8,13 +9,12 @@ export function inputs(instance: any, el: ElementRef, props: string[]) {
}); });
} }
export function proxyEvent<T>(emitter: EventEmitter<T>, el: EventTarget, eventName: string) { export function proxyEvent<T>(emitter: Subject<T>, el: EventTarget, eventName: string) {
el.addEventListener(eventName, (ev) => { el.addEventListener(eventName, (ev) => {
emitter.emit(ev ? (ev as any).detail as T : undefined); emitter.next(ev ? (ev as any).detail as T : undefined);
}); });
} }
export function proxyMethod(ctrlName: string, methodName: string, ...args: any[]) { export function proxyMethod(ctrlName: string, methodName: string, ...args: any[]) {
const controller = ensureElementInBody(ctrlName); const controller = ensureElementInBody(ctrlName);
return controller.componentOnReady() return controller.componentOnReady()