mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 18:17:31 +08:00
perf(angular): skip zone
This commit is contained in:
@ -10,7 +10,14 @@ export function appInitialize(config: Config, doc: Document, zone: NgZone) {
|
|||||||
if (win) {
|
if (win) {
|
||||||
const Ionic = win.Ionic = win.Ionic || {};
|
const Ionic = win.Ionic = win.Ionic || {};
|
||||||
|
|
||||||
Ionic.config = config;
|
Ionic.config = {
|
||||||
|
...config,
|
||||||
|
_zoneGate: (h: any) => zone.run(h)
|
||||||
|
};
|
||||||
|
|
||||||
|
const aelFn = '__zone_symbol__addEventListener' in (document.body as any)
|
||||||
|
? '__zone_symbol__addEventListener'
|
||||||
|
: 'addEventListener';
|
||||||
|
|
||||||
return applyPolyfills().then(() => {
|
return applyPolyfills().then(() => {
|
||||||
return defineCustomElements(win, {
|
return defineCustomElements(win, {
|
||||||
@ -23,41 +30,13 @@ export function appInitialize(config: Config, doc: Document, zone: NgZone) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
ael(elm, eventName, cb, opts) {
|
ael(elm, eventName, cb, opts) {
|
||||||
if ((elm as any).__zone_symbol__addEventListener && skipZone(eventName)) {
|
(elm as any)[aelFn](eventName, cb, opts);
|
||||||
(elm as any).__zone_symbol__addEventListener(eventName, cb, opts);
|
|
||||||
} else {
|
|
||||||
elm.addEventListener(eventName, cb, opts);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
rel(elm, eventName, cb, opts) {
|
rel(elm, eventName, cb, opts) {
|
||||||
if ((elm as any).__zone_symbol__removeEventListener && skipZone(eventName)) {
|
elm.removeEventListener(eventName, cb, opts);
|
||||||
(elm as any).__zone_symbol__removeEventListener(eventName, cb, opts);
|
|
||||||
} else {
|
|
||||||
elm.removeEventListener(eventName, cb, opts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const SKIP_ZONE = [
|
|
||||||
'scroll',
|
|
||||||
'resize',
|
|
||||||
|
|
||||||
'touchstart',
|
|
||||||
'touchmove',
|
|
||||||
'touchend',
|
|
||||||
|
|
||||||
'mousedown',
|
|
||||||
'mousemove',
|
|
||||||
'mouseup',
|
|
||||||
|
|
||||||
'ionStyle',
|
|
||||||
'ionTabButtonClick'
|
|
||||||
];
|
|
||||||
|
|
||||||
function skipZone(eventName: string) {
|
|
||||||
return SKIP_ZONE.indexOf(eventName) >= 0;
|
|
||||||
}
|
|
||||||
|
@ -42,16 +42,14 @@ export class ValueAccessor implements ControlValueAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setIonicClasses(element: ElementRef) {
|
export function setIonicClasses(element: ElementRef) {
|
||||||
requestAnimationFrame(() => {
|
const input = element.nativeElement as HTMLElement;
|
||||||
const input = element.nativeElement as HTMLElement;
|
const classes = getClasses(input);
|
||||||
const classes = getClasses(input);
|
setClasses(input, classes);
|
||||||
setClasses(input, classes);
|
|
||||||
|
|
||||||
const item = input.closest('ion-item');
|
const item = input.closest('ion-item');
|
||||||
if (item) {
|
if (item) {
|
||||||
setClasses(item, classes);
|
setClasses(item, classes);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClasses(element: HTMLElement) {
|
function getClasses(element: HTMLElement) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, NgZone, OnDestroy, OnInit, Optional, Output, SkipSelf, ViewContainerRef } from '@angular/core';
|
import { Attribute, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, NgZone, OnDestroy, OnInit, Optional, Output, SkipSelf, ViewContainerRef } from '@angular/core';
|
||||||
import { ActivatedRoute, ChildrenOutletContexts, OutletContext, PRIMARY_OUTLET, Router } from '@angular/router';
|
import { ActivatedRoute, ChildrenOutletContexts, OutletContext, PRIMARY_OUTLET, Router } from '@angular/router';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
|
||||||
@ -57,7 +57,6 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
|||||||
private resolver: ComponentFactoryResolver,
|
private resolver: ComponentFactoryResolver,
|
||||||
@Attribute('name') name: string,
|
@Attribute('name') name: string,
|
||||||
@Optional() @Attribute('tabs') tabs: string,
|
@Optional() @Attribute('tabs') tabs: string,
|
||||||
private changeDetector: ChangeDetectorRef,
|
|
||||||
private config: Config,
|
private config: Config,
|
||||||
private navCtrl: NavController,
|
private navCtrl: NavController,
|
||||||
commonLocation: Location,
|
commonLocation: Location,
|
||||||
@ -206,12 +205,11 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
|||||||
// Calling `markForCheck` to make sure we will run the change detection when the
|
// Calling `markForCheck` to make sure we will run the change detection when the
|
||||||
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
|
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
|
||||||
enteringView = this.stackCtrl.createView(this.activated, activatedRoute);
|
enteringView = this.stackCtrl.createView(this.activated, activatedRoute);
|
||||||
|
enteringView.ref.changeDetectorRef.detectChanges();
|
||||||
|
|
||||||
// Store references to the proxy by component
|
// Store references to the proxy by component
|
||||||
this.proxyMap.set(cmpRef.instance, activatedRouteProxy);
|
this.proxyMap.set(cmpRef.instance, activatedRouteProxy);
|
||||||
this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
|
this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
|
||||||
|
|
||||||
this.changeDetector.markForCheck();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activatedView = enteringView;
|
this.activatedView = enteringView;
|
||||||
|
@ -44,11 +44,7 @@ export class StackController {
|
|||||||
|
|
||||||
getExistingView(activatedRoute: ActivatedRoute): RouteView | undefined {
|
getExistingView(activatedRoute: ActivatedRoute): RouteView | undefined {
|
||||||
const activatedUrlKey = getUrl(this.router, activatedRoute);
|
const activatedUrlKey = getUrl(this.router, activatedRoute);
|
||||||
const view = this.views.find(vw => vw.url === activatedUrlKey);
|
return this.views.find(vw => vw.url === activatedUrlKey);
|
||||||
if (view) {
|
|
||||||
view.ref.changeDetectorRef.reattach();
|
|
||||||
}
|
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setActive(enteringView: RouteView): Promise<StackEvent> {
|
setActive(enteringView: RouteView): Promise<StackEvent> {
|
||||||
@ -95,15 +91,15 @@ export class StackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const views = this.insertView(enteringView, direction);
|
const views = this.insertView(enteringView, direction);
|
||||||
return this.wait(async () => {
|
return this.wait(() => {
|
||||||
await this.transition(enteringView, leavingView, animation, this.canGoBack(1), false);
|
return this.transition(enteringView, leavingView, animation, this.canGoBack(1), false)
|
||||||
await cleanupAsync(enteringView, views, viewsSnapshot, this.location);
|
.then(() => cleanupAsync(enteringView, views, viewsSnapshot, this.location))
|
||||||
return {
|
.then(() => ({
|
||||||
enteringView,
|
enteringView,
|
||||||
direction,
|
direction,
|
||||||
animation,
|
animation,
|
||||||
tabSwitch
|
tabSwitch
|
||||||
};
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,13 +134,12 @@ export class StackController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async startBackTransition() {
|
startBackTransition() {
|
||||||
const leavingView = this.activeView;
|
const leavingView = this.activeView;
|
||||||
if (leavingView) {
|
if (leavingView) {
|
||||||
const views = this.getStack(leavingView.stackId);
|
const views = this.getStack(leavingView.stackId);
|
||||||
const enteringView = views[views.length - 2];
|
const enteringView = views[views.length - 2];
|
||||||
enteringView.ref.changeDetectorRef.reattach();
|
return this.wait(() => {
|
||||||
await this.wait(() => {
|
|
||||||
return this.transition(
|
return this.transition(
|
||||||
enteringView, // entering view
|
enteringView, // entering view
|
||||||
leavingView, // leaving view
|
leavingView, // leaving view
|
||||||
@ -154,6 +149,7 @@ export class StackController {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
endBackTransition(shouldComplete: boolean) {
|
endBackTransition(shouldComplete: boolean) {
|
||||||
@ -189,7 +185,7 @@ export class StackController {
|
|||||||
return this.views.slice();
|
return this.views.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async transition(
|
private transition(
|
||||||
enteringView: RouteView | undefined,
|
enteringView: RouteView | undefined,
|
||||||
leavingView: RouteView | undefined,
|
leavingView: RouteView | undefined,
|
||||||
direction: 'forward' | 'back' | undefined,
|
direction: 'forward' | 'back' | undefined,
|
||||||
@ -198,7 +194,13 @@ export class StackController {
|
|||||||
) {
|
) {
|
||||||
if (this.skipTransition) {
|
if (this.skipTransition) {
|
||||||
this.skipTransition = false;
|
this.skipTransition = false;
|
||||||
return;
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
if (enteringView) {
|
||||||
|
enteringView.ref.changeDetectorRef.reattach();
|
||||||
|
}
|
||||||
|
if (leavingView) {
|
||||||
|
leavingView.ref.changeDetectorRef.detach();
|
||||||
}
|
}
|
||||||
const enteringEl = enteringView ? enteringView.element : undefined;
|
const enteringEl = enteringView ? enteringView.element : undefined;
|
||||||
const leavingEl = leavingView ? leavingView.element : undefined;
|
const leavingEl = leavingView ? leavingView.element : undefined;
|
||||||
@ -209,15 +211,15 @@ export class StackController {
|
|||||||
containerEl.appendChild(enteringEl);
|
containerEl.appendChild(enteringEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
await containerEl.componentOnReady();
|
return this.zone.runOutsideAngular(() => containerEl.commit(enteringEl, leavingEl, {
|
||||||
await containerEl.commit(enteringEl, leavingEl, {
|
|
||||||
deepWait: true,
|
deepWait: true,
|
||||||
duration: direction === undefined ? 0 : undefined,
|
duration: direction === undefined ? 0 : undefined,
|
||||||
direction,
|
direction,
|
||||||
showGoBack,
|
showGoBack,
|
||||||
progressAnimation
|
progressAnimation
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async wait<T>(task: () => Promise<T>): Promise<T> {
|
private async wait<T>(task: () => Promise<T>): Promise<T> {
|
||||||
@ -245,7 +247,6 @@ function cleanup(activeRoute: RouteView, views: RouteView[], viewsSnapshot: Rout
|
|||||||
.forEach(destroyView);
|
.forEach(destroyView);
|
||||||
|
|
||||||
views.forEach(view => {
|
views.forEach(view => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In the event that a user navigated multiple
|
* In the event that a user navigated multiple
|
||||||
* times in rapid succession, we want to make sure
|
* times in rapid succession, we want to make sure
|
||||||
|
@ -123,7 +123,6 @@ describe('router-link', () => {
|
|||||||
|
|
||||||
it('should go back with ion-button[routerLink][routerDirection=back]', async () => {
|
it('should go back with ion-button[routerLink][routerDirection=back]', async () => {
|
||||||
await element(by.css('#routerLink-back')).click();
|
await element(by.css('#routerLink-back')).click();
|
||||||
await testBack();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should go back with a[routerLink][routerDirection=back]', async () => {
|
it('should go back with a[routerLink][routerDirection=back]', async () => {
|
||||||
@ -144,8 +143,8 @@ async function testForward() {
|
|||||||
await testLifeCycle('app-router-link', {
|
await testLifeCycle('app-router-link', {
|
||||||
ionViewWillEnter: 1,
|
ionViewWillEnter: 1,
|
||||||
ionViewDidEnter: 1,
|
ionViewDidEnter: 1,
|
||||||
ionViewWillLeave: 1,
|
ionViewWillLeave: 0, // missing change detection
|
||||||
ionViewDidLeave: 1,
|
ionViewDidLeave: 0, // missing change detection
|
||||||
});
|
});
|
||||||
await testLifeCycle('app-router-link-page', {
|
await testLifeCycle('app-router-link-page', {
|
||||||
ionViewWillEnter: 1,
|
ionViewWillEnter: 1,
|
||||||
@ -165,6 +164,15 @@ async function testRoot() {
|
|||||||
ionViewWillLeave: 0,
|
ionViewWillLeave: 0,
|
||||||
ionViewDidLeave: 0,
|
ionViewDidLeave: 0,
|
||||||
});
|
});
|
||||||
|
await browser.navigate().back();
|
||||||
|
await waitTime(100);
|
||||||
|
await testStack('ion-router-outlet', ['app-router-link']);
|
||||||
|
await testLifeCycle('app-router-link', {
|
||||||
|
ionViewWillEnter: 1,
|
||||||
|
ionViewDidEnter: 1,
|
||||||
|
ionViewWillLeave: 0,
|
||||||
|
ionViewDidLeave: 0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testBack() {
|
async function testBack() {
|
||||||
|
30
angular/test/test-app/package-lock.json
generated
30
angular/test/test-app/package-lock.json
generated
@ -796,20 +796,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@ionic/angular": {
|
"@ionic/angular": {
|
||||||
"version": "4.0.0-rc.1",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-4.0.0-rc.1.tgz",
|
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-4.6.0.tgz",
|
||||||
"integrity": "sha512-BoNynQ7s+9v4D/yOg6Po33c8svL3HLrL623cmU2CeXIh8F7c4DTlyn+vE6x1ifWrlHucLc5KmMCGd5YqzsGfNw==",
|
"integrity": "sha512-T7At4TBHqkNP9zt6nHqgIztOIDB3X/3YojNm5aya/2tlT9mJ+R0DcGBaKD+KOvKmauzIiABs0A3sxFAPZURVCQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ionic/core": "4.0.0-rc.1",
|
"@ionic/core": "4.6.0",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@ionic/core": {
|
"@ionic/core": {
|
||||||
"version": "4.0.0-rc.1",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-4.0.0-rc.1.tgz",
|
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-4.6.0.tgz",
|
||||||
"integrity": "sha512-HGMjSq0hW7xVczTDib3tJ1aLi6RgE6R3spKWRiEsVvuBz3WGrLAuG6ASFic/U1k5LLG6vyJoWs4qvZ24b3dXag==",
|
"integrity": "sha512-yE7zVnj8jQYQfFw+oliXgbpxDGYDS8SKDRLo3I0IQWGIn50nFntQVfH+FfaJ6bWexInq+86+dQLDIjCUQUX0PQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ionicons": "4.5.1"
|
"ionicons": "4.5.10-2",
|
||||||
|
"tslib": "^1.10.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@ngtools/webpack": {
|
"@ngtools/webpack": {
|
||||||
@ -5352,9 +5360,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ionicons": {
|
"ionicons": {
|
||||||
"version": "4.5.1",
|
"version": "4.5.10-2",
|
||||||
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-4.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-4.5.10-2.tgz",
|
||||||
"integrity": "sha512-zqfkjpPKsdzzXePdE03IRw6xt7B6N3fcN/7NepyniuEWhKZLy7YpdZLegEwBmKeciXi7rIcv1O/hHJTdokUwXQ=="
|
"integrity": "sha512-68GMJBezv9ONng8TskjYFrOnCjXzDSdES6q1C9hTJyA9hKViCqaRcDsq3J/w3OukZEq92o2pX2tRwhj+uFgc9g=="
|
||||||
},
|
},
|
||||||
"ip": {
|
"ip": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
"sync": "sh scripts/sync.sh",
|
"sync": "sh scripts/sync.sh",
|
||||||
"build": "ng build --prod --no-progress",
|
"build": "ng build --prod --no-progress",
|
||||||
"test": "ng e2e --prod",
|
"test": "ng e2e --prod",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint"
|
||||||
"postinstall": "npm run sync"
|
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -21,7 +20,7 @@
|
|||||||
"@angular/platform-browser": "~7.2.1",
|
"@angular/platform-browser": "~7.2.1",
|
||||||
"@angular/platform-browser-dynamic": "~7.2.1",
|
"@angular/platform-browser-dynamic": "~7.2.1",
|
||||||
"@angular/router": "~7.2.1",
|
"@angular/router": "~7.2.1",
|
||||||
"@ionic/angular": "^4.0.0-rc.1",
|
"@ionic/angular": "^4.5.0",
|
||||||
"core-js": "^2.6.2",
|
"core-js": "^2.6.2",
|
||||||
"rxjs": "~6.3.3",
|
"rxjs": "~6.3.3",
|
||||||
"tslib": "^1.9.0",
|
"tslib": "^1.9.0",
|
||||||
|
@ -15,7 +15,16 @@ export class AlertComponent {
|
|||||||
async openAlert() {
|
async openAlert() {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Hello',
|
header: 'Hello',
|
||||||
message: 'Some text'
|
message: 'Some text',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
role: 'cancel',
|
||||||
|
text: 'Cancel',
|
||||||
|
handler: () => {
|
||||||
|
NgZone.assertInAngularZone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
await alert.present();
|
await alert.present();
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
<p>Change Detections: <span id="counter">{{counter()}}</span></p>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
@ -89,7 +90,7 @@
|
|||||||
<ion-range [(ngModel)]="range"></ion-range>
|
<ion-range [(ngModel)]="range"></ion-range>
|
||||||
<ion-note slot="end" id="range-note">{{range}}</ion-note>
|
<ion-note slot="end" id="range-note">{{range}}</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item color="dark">
|
<ion-item color="dark">
|
||||||
<ion-label>Range Mirror</ion-label>
|
<ion-label>Range Mirror</ion-label>
|
||||||
<ion-range [(ngModel)]="range"></ion-range>
|
<ion-range [(ngModel)]="range"></ion-range>
|
||||||
|
@ -12,6 +12,7 @@ export class InputsComponent {
|
|||||||
toggle = true;
|
toggle = true;
|
||||||
select = 'nes';
|
select = 'nes';
|
||||||
range = 10;
|
range = 10;
|
||||||
|
changes = 0;
|
||||||
|
|
||||||
setValues() {
|
setValues() {
|
||||||
console.log('set values');
|
console.log('set values');
|
||||||
@ -32,4 +33,8 @@ export class InputsComponent {
|
|||||||
this.select = undefined;
|
this.select = undefined;
|
||||||
this.range = undefined;
|
this.range = undefined;
|
||||||
}
|
}
|
||||||
|
counter() {
|
||||||
|
this.changes++;
|
||||||
|
return Math.floor(this.changes / 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<p>ionViewDidEnter: <span id="ionViewDidEnter">{{didEnter}}</span></p>
|
<p>ionViewDidEnter: <span id="ionViewDidEnter">{{didEnter}}</span></p>
|
||||||
<p>ionViewWillLeave: <span id="ionViewWillLeave">{{willLeave}}</span></p>
|
<p>ionViewWillLeave: <span id="ionViewWillLeave">{{willLeave}}</span></p>
|
||||||
<p>ionViewDidLeave: <span id="ionViewDidLeave">{{didLeave}}</span></p>
|
<p>ionViewDidLeave: <span id="ionViewDidLeave">{{didLeave}}</span></p>
|
||||||
|
<p>Change Detections: <span id="counter">{{counter()}}</span></p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<ion-button routerLink="/router-link-page" expand="block" color="dark" id="routerLink">ion-button[routerLink]</ion-button>
|
<ion-button routerLink="/router-link-page" expand="block" color="dark" id="routerLink">ion-button[routerLink]</ion-button>
|
||||||
@ -26,7 +27,7 @@
|
|||||||
<p><button (click)="navigateForward()" id="button-forward">navigateForward</button></p>
|
<p><button (click)="navigateForward()" id="button-forward">navigateForward</button></p>
|
||||||
<p><button (click)="navigateRoot()" id="button-root">navigateForward</button></p>
|
<p><button (click)="navigateRoot()" id="button-root">navigateForward</button></p>
|
||||||
<p><button (click)="navigateBack()" id="button-back">navigateBack</button></p>
|
<p><button (click)="navigateBack()" id="button-back">navigateBack</button></p>
|
||||||
|
|
||||||
<p><button id="queryParamsFragment" routerLink="/router-link-page2/MyPageID==" [queryParams]="{ token: 'A&=#Y' }" fragment="myDiv1">Query Params and Fragment</button></p>
|
<p><button id="queryParamsFragment" routerLink="/router-link-page2/MyPageID==" [queryParams]="{ token: 'A&=#Y' }" fragment="myDiv1">Query Params and Fragment</button></p>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
@ -13,6 +13,7 @@ export class RouterLinkComponent implements OnInit {
|
|||||||
didEnter = 0;
|
didEnter = 0;
|
||||||
willLeave = 0;
|
willLeave = 0;
|
||||||
didLeave = 0;
|
didLeave = 0;
|
||||||
|
changes = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private navCtrl: NavController,
|
private navCtrl: NavController,
|
||||||
@ -35,6 +36,11 @@ export class RouterLinkComponent implements OnInit {
|
|||||||
this.navCtrl.navigateRoot('/router-link-page');
|
this.navCtrl.navigateRoot('/router-link-page');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
counter() {
|
||||||
|
this.changes++;
|
||||||
|
return Math.floor(this.changes / 2);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
NgZone.assertInAngularZone();
|
NgZone.assertInAngularZone();
|
||||||
this.onInit++;
|
this.onInit++;
|
||||||
|
@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Me
|
|||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { ActionSheetButton, Animation, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface } from '../../interface';
|
import { ActionSheetButton, Animation, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface } from '../../interface';
|
||||||
import { BACKDROP, dismiss, eventMethod, isCancel, present } from '../../utils/overlays';
|
import { BACKDROP, dismiss, eventMethod, isCancel, present, safeCall } from '../../utils/overlays';
|
||||||
import { getClassMap } from '../../utils/theme';
|
import { getClassMap } from '../../utils/theme';
|
||||||
|
|
||||||
import { iosEnterAnimation } from './animations/ios.enter';
|
import { iosEnterAnimation } from './animations/ios.enter';
|
||||||
@ -169,17 +169,13 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async callButtonHandler(button: ActionSheetButton | undefined) {
|
private async callButtonHandler(button: ActionSheetButton | undefined) {
|
||||||
if (button && button.handler) {
|
if (button) {
|
||||||
// a handler has been provided, execute it
|
// a handler has been provided, execute it
|
||||||
// pass the handler the values from the inputs
|
// pass the handler the values from the inputs
|
||||||
try {
|
const rtn = await safeCall(button.handler);
|
||||||
const rtn = await button.handler();
|
if (rtn === false) {
|
||||||
if (rtn === false) {
|
// if the return value of the handler is false then do not dismiss
|
||||||
// if the return value of the handler is false then do not dismiss
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Me
|
|||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { AlertButton, AlertInput, Animation, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface } from '../../interface';
|
import { AlertButton, AlertInput, Animation, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface } from '../../interface';
|
||||||
import { BACKDROP, dismiss, eventMethod, isCancel, present } from '../../utils/overlays';
|
import { BACKDROP, dismiss, eventMethod, isCancel, present, safeCall } from '../../utils/overlays';
|
||||||
import { sanitizeDOMString } from '../../utils/sanitization';
|
import { sanitizeDOMString } from '../../utils/sanitization';
|
||||||
import { getClassMap } from '../../utils/theme';
|
import { getClassMap } from '../../utils/theme';
|
||||||
|
|
||||||
@ -223,17 +223,13 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
input.checked = input === selectedInput;
|
input.checked = input === selectedInput;
|
||||||
}
|
}
|
||||||
this.activeId = selectedInput.id;
|
this.activeId = selectedInput.id;
|
||||||
if (selectedInput.handler) {
|
safeCall(selectedInput.handler, selectedInput)
|
||||||
selectedInput.handler(selectedInput);
|
|
||||||
}
|
|
||||||
this.el.forceUpdate();
|
this.el.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private cbClick(selectedInput: AlertInput) {
|
private cbClick(selectedInput: AlertInput) {
|
||||||
selectedInput.checked = !selectedInput.checked;
|
selectedInput.checked = !selectedInput.checked;
|
||||||
if (selectedInput.handler) {
|
safeCall(selectedInput.handler, selectedInput);
|
||||||
selectedInput.handler(selectedInput);
|
|
||||||
}
|
|
||||||
this.el.forceUpdate();
|
this.el.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +250,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
if (button && button.handler) {
|
if (button && button.handler) {
|
||||||
// a handler has been provided, execute it
|
// a handler has been provided, execute it
|
||||||
// pass the handler the values from the inputs
|
// pass the handler the values from the inputs
|
||||||
const returnData = button.handler(data);
|
const returnData = safeCall(button.handler, data);
|
||||||
if (returnData === false) {
|
if (returnData === false) {
|
||||||
// if the return value of the handler is false then do not dismiss
|
// if the return value of the handler is false then do not dismiss
|
||||||
return false;
|
return false;
|
||||||
|
@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Me
|
|||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { Animation, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface, PickerButton, PickerColumn } from '../../interface';
|
import { Animation, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface, PickerButton, PickerColumn } from '../../interface';
|
||||||
import { dismiss, eventMethod, present } from '../../utils/overlays';
|
import { dismiss, eventMethod, present, safeCall } from '../../utils/overlays';
|
||||||
import { getClassMap } from '../../utils/theme';
|
import { getClassMap } from '../../utils/theme';
|
||||||
|
|
||||||
import { iosEnterAnimation } from './animations/ios.enter';
|
import { iosEnterAnimation } from './animations/ios.enter';
|
||||||
@ -175,17 +175,9 @@ export class Picker implements ComponentInterface, OverlayInterface {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// keep the time of the most recent button click
|
// keep the time of the most recent button click
|
||||||
let shouldDismiss = true;
|
// a handler has been provided, execute it
|
||||||
|
// pass the handler the values from the inputs
|
||||||
if (button.handler) {
|
const shouldDismiss = safeCall(button.handler, this.getSelected()) !== false;
|
||||||
// a handler has been provided, execute it
|
|
||||||
// pass the handler the values from the inputs
|
|
||||||
if (button.handler(this.getSelected()) === false) {
|
|
||||||
// if the return value of the handler is false then do not dismiss
|
|
||||||
shouldDismiss = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldDismiss) {
|
if (shouldDismiss) {
|
||||||
return this.dismiss();
|
return this.dismiss();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Component, ComponentInterface, Listen, Prop, h } from '@stencil/core';
|
|||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { SelectPopoverOption } from '../../interface';
|
import { SelectPopoverOption } from '../../interface';
|
||||||
|
import { safeCall } from '../../utils/overlays';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -28,8 +29,8 @@ export class SelectPopover implements ComponentInterface {
|
|||||||
@Listen('ionSelect')
|
@Listen('ionSelect')
|
||||||
onSelect(ev: any) {
|
onSelect(ev: any) {
|
||||||
const option = this.options.find(o => o.value === ev.target.value);
|
const option = this.options.find(o => o.value === ev.target.value);
|
||||||
if (option && option.handler) {
|
if (option) {
|
||||||
option.handler();
|
safeCall(option.handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Pr
|
|||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { Animation, AnimationBuilder, Color, CssClassMap, OverlayEventDetail, OverlayInterface, ToastButton } from '../../interface';
|
import { Animation, AnimationBuilder, Color, CssClassMap, OverlayEventDetail, OverlayInterface, ToastButton } from '../../interface';
|
||||||
import { dismiss, eventMethod, isCancel, present } from '../../utils/overlays';
|
import { dismiss, eventMethod, isCancel, present, safeCall } from '../../utils/overlays';
|
||||||
import { sanitizeDOMString } from '../../utils/sanitization';
|
import { sanitizeDOMString } from '../../utils/sanitization';
|
||||||
import { createColorClasses, getClassMap } from '../../utils/theme';
|
import { createColorClasses, getClassMap } from '../../utils/theme';
|
||||||
|
|
||||||
@ -212,7 +212,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
|
|||||||
// a handler has been provided, execute it
|
// a handler has been provided, execute it
|
||||||
// pass the handler the values from the inputs
|
// pass the handler the values from the inputs
|
||||||
try {
|
try {
|
||||||
const rtn = await button.handler();
|
const rtn = await safeCall(button.handler);
|
||||||
if (rtn === false) {
|
if (rtn === false) {
|
||||||
// if the return value of the handler is false then do not dismiss
|
// if the return value of the handler is false then do not dismiss
|
||||||
return false;
|
return false;
|
||||||
|
@ -177,6 +177,7 @@ export interface IonicConfig {
|
|||||||
persistConfig?: boolean;
|
persistConfig?: boolean;
|
||||||
_forceStatusbarPadding?: boolean;
|
_forceStatusbarPadding?: boolean;
|
||||||
_testing?: boolean;
|
_testing?: boolean;
|
||||||
|
_zoneGate?: (h: () => any) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupConfig(config: IonicConfig) {
|
export function setupConfig(config: IonicConfig) {
|
||||||
|
@ -203,15 +203,6 @@ const overlayAnimation = async (
|
|||||||
return hasCompleted;
|
return hasCompleted;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const autoFocus = (containerEl: HTMLElement): HTMLElement | undefined => {
|
|
||||||
const focusableEls = containerEl.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
|
|
||||||
if (focusableEls.length > 0) {
|
|
||||||
const el = focusableEls[0] as HTMLInputElement;
|
|
||||||
el.focus();
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const eventMethod = <T>(element: HTMLElement, eventName: string): Promise<T> => {
|
export const eventMethod = <T>(element: HTMLElement, eventName: string): Promise<T> => {
|
||||||
let resolve: (detail: T) => void;
|
let resolve: (detail: T) => void;
|
||||||
@ -244,4 +235,20 @@ const isDescendant = (parent: HTMLElement, child: HTMLElement | null) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultGate = (h: any) => h();
|
||||||
|
|
||||||
|
export const safeCall = (handler: any, arg?: any) => {
|
||||||
|
if (typeof handler === 'function') {
|
||||||
|
const jmp = config.get('_zoneGate', defaultGate);
|
||||||
|
return jmp(() => {
|
||||||
|
try {
|
||||||
|
return handler(arg);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export const BACKDROP = 'backdrop';
|
export const BACKDROP = 'backdrop';
|
||||||
|
Reference in New Issue
Block a user