fix(router-outlet): change detection fires properly (#18896)

* fix(router-outlet): never detach() the entering view

fixes #18894

* add tests

* ci

* update package-lock

* circle sync runtime
This commit is contained in:
Manu MA
2019-07-26 17:13:50 +02:00
committed by Liam DeBeasi
parent 462cee5b2e
commit 962783bfba
22 changed files with 3543 additions and 2728 deletions

View File

@ -173,6 +173,9 @@ jobs:
- run: - run:
command: npm install command: npm install
working_directory: /tmp/workspace/angular/test/test-app working_directory: /tmp/workspace/angular/test/test-app
- run:
command: npm run sync
working_directory: /tmp/workspace/angular/test/test-app
- run: - run:
command: npm test command: npm test
working_directory: /tmp/workspace/angular/test/test-app working_directory: /tmp/workspace/angular/test/test-app

View File

@ -44,7 +44,11 @@ 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);
return this.views.find(vw => vw.url === activatedUrlKey); const view = 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> {
@ -55,6 +59,7 @@ export class StackController {
direction = 'back'; direction = 'back';
animation = undefined; animation = undefined;
} }
const viewsSnapshot = this.views.slice(); const viewsSnapshot = this.views.slice();
let currentNavigation; let currentNavigation;
@ -208,14 +213,19 @@ export class StackController {
this.skipTransition = false; this.skipTransition = false;
return Promise.resolve(false); return Promise.resolve(false);
} }
if (enteringView) { if (leavingView === enteringView) {
enteringView.ref.changeDetectorRef.reattach(); return Promise.resolve(false);
} }
// disconnect leaving page from change detection to // disconnect leaving page from change detection to
// reduce jank during the page transition // reduce jank during the page transition
if (leavingView) { if (leavingView) {
leavingView.ref.changeDetectorRef.detach(); leavingView.ref.changeDetectorRef.detach();
} }
// In case the enteringView is the same as the leavingPage we need to reattach()
if (enteringView) {
enteringView.ref.changeDetectorRef.reattach();
}
const enteringEl = enteringView ? enteringView.element : undefined; const enteringEl = enteringView ? enteringView.element : undefined;
const leavingEl = leavingView ? leavingView.element : undefined; const leavingEl = leavingView ? leavingView.element : undefined;
const containerEl = this.containerEl; const containerEl = this.containerEl;

View File

@ -25,9 +25,6 @@ export class IonicRouteStrategy implements RouteReuseStrategy {
if (future.routeConfig !== curr.routeConfig) { if (future.routeConfig !== curr.routeConfig) {
return false; return false;
} }
if (future.component !== curr.component) {
return false;
}
// checking router params // checking router params
const futureParams = future.params; const futureParams = future.params;

View File

@ -20,7 +20,7 @@ describe('tabs', () => {
it('should simulate stack + double tab click', async () => { it('should simulate stack + double tab click', async () => {
let tab = await getSelectedTab() as ElementFinder; let tab = await getSelectedTab() as ElementFinder;
await tab.$('#goto-tab1-page2').click(); await tab.$('#goto-tab1-page2').click();
await testTabTitle('Tab 1 - Page 2'); await testTabTitle('Tab 1 - Page 2 (1)');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested']); await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested']);
await testState(1, 'account'); await testState(1, 'account');
expect(await tab.$('ion-back-button').isDisplayed()).toBe(true); expect(await tab.$('ion-back-button').isDisplayed()).toBe(true);
@ -31,7 +31,7 @@ describe('tabs', () => {
await testState(2, 'contact'); await testState(2, 'contact');
await element(by.css('#tab-button-account')).click(); await element(by.css('#tab-button-account')).click();
tab = await testTabTitle('Tab 1 - Page 2'); tab = await testTabTitle('Tab 1 - Page 2 (1)');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']); await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']);
await testState(3, 'account'); await testState(3, 'account');
expect(await tab.$('ion-back-button').isDisplayed()).toBe(true); expect(await tab.$('ion-back-button').isDisplayed()).toBe(true);
@ -45,7 +45,7 @@ describe('tabs', () => {
it('should simulate stack + back button click', async () => { it('should simulate stack + back button click', async () => {
const tab = await getSelectedTab(); const tab = await getSelectedTab();
await tab.$('#goto-tab1-page2').click(); await tab.$('#goto-tab1-page2').click();
await testTabTitle('Tab 1 - Page 2'); await testTabTitle('Tab 1 - Page 2 (1)');
await testState(1, 'account'); await testState(1, 'account');
await element(by.css('#tab-button-contact')).click(); await element(by.css('#tab-button-contact')).click();
@ -53,7 +53,7 @@ describe('tabs', () => {
await testState(2, 'contact'); await testState(2, 'contact');
await element(by.css('#tab-button-account')).click(); await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 2'); await testTabTitle('Tab 1 - Page 2 (1)');
await testState(3, 'account'); await testState(3, 'account');
await element(by.css('ion-back-button')).click(); await element(by.css('ion-back-button')).click();
@ -62,6 +62,33 @@ describe('tabs', () => {
await testState(3, 'account'); await testState(3, 'account');
}); });
it('should navigate deep then go home', async () => {
let tab = await getSelectedTab();
await tab.$('#goto-tab1-page2').click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
await tab.$('#goto-next').click();
tab = await testTabTitle('Tab 1 - Page 2 (2)');
await element(by.css('#tab-button-contact')).click();
tab = await testTabTitle('Tab 2 - Page 1');
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 2 (2)');
await testStack('ion-tabs ion-router-outlet', [
'app-tabs-tab1',
'app-tabs-tab1-nested',
'app-tabs-tab1-nested',
'app-tabs-tab2'
]);
await element(by.css('#tab-button-account')).click();
await testTabTitle('Tab 1 - Page 1');
await testStack('ion-tabs ion-router-outlet', [
'app-tabs-tab1',
'app-tabs-tab2'
]);
});
it('should switch tabs and go back', async () => { it('should switch tabs and go back', async () => {
await element(by.css('#tab-button-contact')).click(); await element(by.css('#tab-button-contact')).click();
const tab = await testTabTitle('Tab 2 - Page 1'); const tab = await testTabTitle('Tab 2 - Page 1');
@ -76,7 +103,7 @@ describe('tabs', () => {
const tab = await testTabTitle('Tab 2 - Page 1'); const tab = await testTabTitle('Tab 2 - Page 1');
await tab.$('#goto-tab1-page2').click(); await tab.$('#goto-tab1-page2').click();
await testTabTitle('Tab 1 - Page 2'); await testTabTitle('Tab 1 - Page 2 (1)');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']); await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']);
}); });
@ -97,14 +124,14 @@ describe('tabs', () => {
}); });
}); });
describe('entry url - /tabs/account/nested/12', () => { describe('entry url - /tabs/account/nested/1', () => {
beforeEach(async () => { beforeEach(async () => {
await browser.get('/tabs/account/nested/12'); await browser.get('/tabs/account/nested/1');
await waitTime(30); await waitTime(30);
}); });
it('should only display the back-button when there is a page in the stack', async () => { it('should only display the back-button when there is a page in the stack', async () => {
let tab = await testTabTitle('Tab 1 - Page 2') as ElementFinder; let tab = await testTabTitle('Tab 1 - Page 2 (1)') as ElementFinder;
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']); await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
expect(await tab.$('ion-back-button').isDisplayed()).toBe(false); expect(await tab.$('ion-back-button').isDisplayed()).toBe(false);
@ -112,9 +139,32 @@ describe('tabs', () => {
tab = await testTabTitle('Tab 1 - Page 1'); tab = await testTabTitle('Tab 1 - Page 1');
await tab.$('#goto-tab1-page2').click(); await tab.$('#goto-tab1-page2').click();
tab = await testTabTitle('Tab 1 - Page 2'); tab = await testTabTitle('Tab 1 - Page 2 (1)');
expect(await tab.$('ion-back-button').isDisplayed()).toBe(true); expect(await tab.$('ion-back-button').isDisplayed()).toBe(true);
}); });
it('should not reuse the same page', async () => {
let tab = await testTabTitle('Tab 1 - Page 2 (1)') as ElementFinder;
await tab.$('#goto-next').click();
tab = await testTabTitle('Tab 1 - Page 2 (2)');
await tab.$('#goto-next').click();
tab = await testTabTitle('Tab 1 - Page 2 (3)');
await testStack('ion-tabs ion-router-outlet',[
'app-tabs-tab1-nested',
'app-tabs-tab1-nested',
'app-tabs-tab1-nested'
]);
await tab.$('ion-back-button').click();
tab = await testTabTitle('Tab 1 - Page 2 (2)');
await tab.$('ion-back-button').click();
tab = await testTabTitle('Tab 1 - Page 2 (1)');
expect(await tab.$('ion-back-button').isDisplayed()).toBe(false);
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']);
});
}); });
describe('entry url - /tabs/lazy', () => { describe('entry url - /tabs/lazy', () => {
@ -128,7 +178,7 @@ describe('tabs', () => {
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']); await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']);
await tab.$('#goto-tab1-page2').click(); await tab.$('#goto-tab1-page2').click();
tab = await testTabTitle('Tab 1 - Page 2'); tab = await testTabTitle('Tab 1 - Page 2 (1)');
await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']); await testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']);
expect(await tab.$('ion-back-button').isDisplayed()).toBe(false); expect(await tab.$('ion-back-button').isDisplayed()).toBe(false);
}); });

File diff suppressed because it is too large Load Diff

View File

@ -13,29 +13,28 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~7.2.1", "@angular/animations": "~8.1.2",
"@angular/common": "~7.2.1", "@angular/common": "~8.1.2",
"@angular/compiler": "~7.2.1", "@angular/compiler": "~8.1.2",
"@angular/core": "~7.2.1", "@angular/core": "~8.1.2",
"@angular/forms": "~7.2.1", "@angular/forms": "~8.1.2",
"@angular/platform-browser": "~7.2.1", "@angular/platform-browser": "~8.1.2",
"@angular/platform-browser-dynamic": "~7.2.1", "@angular/platform-browser-dynamic": "~8.1.2",
"@angular/router": "~7.2.1", "@angular/router": "~8.1.2",
"@ionic/angular": "^4.5.0", "@ionic/angular": "^4.7.0",
"core-js": "^2.6.2", "core-js": "^2.6.2",
"rxjs": "~6.3.3", "rxjs": "^6.4.0",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"zone.js": "~0.8.26" "zone.js": "~0.9.1"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.12.2", "@angular-devkit/build-angular": "^0.801.2",
"@angular/cli": "~7.2.1", "@angular/cli": "~8.1.2",
"@angular/compiler-cli": "~7.2.1", "@angular/compiler-cli": "~8.1.2",
"@angular/language-service": "~7.2.1", "@angular/language-service": "~8.1.2",
"@types/jasmine": "~2.8.8", "@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3", "@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4", "@types/node": "~8.9.4",
"codelyzer": "~4.5.0",
"jasmine-core": "~2.99.1", "jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "~4.2.1",
"karma": "~3.1.4", "karma": "~3.1.4",
@ -46,6 +45,6 @@
"protractor": "~5.4.2", "protractor": "~5.4.2",
"ts-node": "~7.0.0", "ts-node": "~7.0.0",
"tslint": "~5.12.1", "tslint": "~5.12.1",
"typescript": "~3.2.4" "typescript": "~3.4.0"
} }
} }

View File

@ -28,6 +28,7 @@ export class AlertComponent {
role: 'cancel', role: 'cancel',
text: 'Cancel', text: 'Cancel',
handler: () => { handler: () => {
console.log(NgZone.isInAngularZone());
NgZone.assertInAngularZone(); NgZone.assertInAngularZone();
} }
} }

View File

@ -7,10 +7,6 @@ import { RouterLinkPageComponent } from './router-link-page/router-link-page.com
import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component'; import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component';
import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component'; import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component';
import { HomePageComponent } from './home-page/home-page.component'; import { HomePageComponent } from './home-page/home-page.component';
import { TabsComponent } from './tabs/tabs.component';
import { TabsTab1Component } from './tabs-tab1/tabs-tab1.component';
import { TabsTab1NestedComponent } from './tabs-tab1-nested/tabs-tab1-nested.component';
import { TabsTab2Component } from './tabs-tab2/tabs-tab2.component';
import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component'; import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component';
import { VirtualScrollDetailComponent } from './virtual-scroll-detail/virtual-scroll-detail.component'; import { VirtualScrollDetailComponent } from './virtual-scroll-detail/virtual-scroll-detail.component';
import { NestedOutletComponent } from './nested-outlet/nested-outlet.component'; import { NestedOutletComponent } from './nested-outlet/nested-outlet.component';
@ -51,40 +47,7 @@ const routes: Routes = [
}, },
{ {
path: 'tabs', path: 'tabs',
component: TabsComponent, loadChildren: './tabs/tabs.module#TabsPageModule'
children: [
{
path: 'account',
children: [
{
path: 'nested/:id',
component: TabsTab1NestedComponent
},
{
path: '',
component: TabsTab1Component
}
]
},
{
path: 'contact',
children: [
{
path: 'one',
component: TabsTab2Component
},
{
path: '',
redirectTo: 'one',
pathMatch: 'full'
}
]
},
{
path: 'lazy',
loadChildren: './tabs-lazy/tabs-lazy.module#TabsLazyModule'
}
]
}, },
{ {
path: 'nested-outlet', path: 'nested-outlet',

View File

@ -1,10 +1,11 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { RouteReuseStrategy } from '@angular/router';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { IonicModule } from '@ionic/angular'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { InputsComponent } from './inputs/inputs.component'; import { InputsComponent } from './inputs/inputs.component';
import { ModalComponent } from './modal/modal.component'; import { ModalComponent } from './modal/modal.component';
@ -14,10 +15,6 @@ import { RouterLinkPageComponent } from './router-link-page/router-link-page.com
import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component'; import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component';
import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component'; import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component';
import { HomePageComponent } from './home-page/home-page.component'; import { HomePageComponent } from './home-page/home-page.component';
import { TabsComponent } from './tabs/tabs.component';
import { TabsTab1Component } from './tabs-tab1/tabs-tab1.component';
import { TabsTab2Component } from './tabs-tab2/tabs-tab2.component';
import { TabsTab1NestedComponent } from './tabs-tab1-nested/tabs-tab1-nested.component';
import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component'; import { VirtualScrollComponent } from './virtual-scroll/virtual-scroll.component';
import { VirtualScrollDetailComponent } from './virtual-scroll-detail/virtual-scroll-detail.component'; import { VirtualScrollDetailComponent } from './virtual-scroll-detail/virtual-scroll-detail.component';
import { VirtualScrollInnerComponent } from './virtual-scroll-inner/virtual-scroll-inner.component'; import { VirtualScrollInnerComponent } from './virtual-scroll-inner/virtual-scroll-inner.component';
@ -45,10 +42,6 @@ import { AlertComponent } from './alert/alert.component';
RouterLinkPage2Component, RouterLinkPage2Component,
RouterLinkPage3Component, RouterLinkPage3Component,
HomePageComponent, HomePageComponent,
TabsComponent,
TabsTab1Component,
TabsTab2Component,
TabsTab1NestedComponent,
VirtualScrollComponent, VirtualScrollComponent,
VirtualScrollDetailComponent, VirtualScrollDetailComponent,
VirtualScrollInnerComponent, VirtualScrollInnerComponent,
@ -76,7 +69,9 @@ import { AlertComponent } from './alert/alert.component';
ModalExampleComponent, ModalExampleComponent,
NavComponent NavComponent
], ],
providers: [], providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule { } export class AppModule { }

View File

@ -6,7 +6,7 @@ import { IonSlides } from '@ionic/angular';
templateUrl: './slides.component.html', templateUrl: './slides.component.html',
}) })
export class SlidesComponent implements AfterViewInit { export class SlidesComponent implements AfterViewInit {
@ViewChild(IonSlides) slides: IonSlides; @ViewChild(IonSlides, {static: true}) slides: IonSlides;
slideIndex = 0; slideIndex = 0;
slideIndex2 = 0; slideIndex2 = 0;

View File

@ -10,6 +10,6 @@
<ion-content padding> <ion-content padding>
<p> <p>
<ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button> <ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button>
<ion-button routerLink="/tabs/account/nested/12" id="goto-tab1-page2">Go to Tab 1 - Page 2</ion-button> <ion-button routerLink="/tabs/account/nested/1" id="goto-tab1-page2">Go to Tab 1 - Page 2</ion-button>
</p> </p>
</ion-content> </ion-content>

View File

@ -8,7 +8,7 @@
<h1>LAZY LOADED TAB</h1> <h1>LAZY LOADED TAB</h1>
<p> <p>
<ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button> <ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button>
<ion-button routerLink="/tabs/account/nested/12" id="goto-tab1-page2">Go to Tab 1 - Page 2</ion-button> <ion-button routerLink="/tabs/account/nested/1" id="goto-tab1-page2">Go to Tab 1 - Page 2</ion-button>
<ion-button routerLink="/tabs/lazy/nested" id="goto-tab3-page2">Go to Tab 3 - Page 2</ion-button> <ion-button routerLink="/tabs/lazy/nested" id="goto-tab3-page2">Go to Tab 3 - Page 2</ion-button>
</p> </p>

View File

@ -1,6 +1,6 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title>Tab 1 - Page 2</ion-title> <ion-title>Tab 1 - Page 2 ({{id}})</ion-title>
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-back-button></ion-back-button> <ion-back-button></ion-back-button>
</ion-buttons> </ion-buttons>
@ -8,9 +8,10 @@
</ion-header> </ion-header>
<ion-content padding> <ion-content padding>
<h1>Welcome to NESTED PAGE</h1> <h1>Welcome to NESTED PAGE {{id}}</h1>
<p> <p>
<ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button> <ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button>
<ion-button routerLink="/tabs/contact" id="goto-tab2-page1">Go to Tab 2 - Page 1</ion-button> <ion-button routerLink="/tabs/contact" id="goto-tab2-page1">Go to Tab 2 - Page 1</ion-button>
<ion-button routerLink="/tabs/account/nested/{{next()}}" id="goto-next">Go to Next</ion-button>
</p> </p>
</ion-content> </ion-content>

View File

@ -1,7 +1,21 @@
import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router';
import { Component, Input } from '@angular/core';
@Component({ @Component({
selector: 'app-tabs-tab1-nested', selector: 'app-tabs-tab1-nested',
templateUrl: './tabs-tab1-nested.component.html', templateUrl: './tabs-tab1-nested.component.html',
}) })
export class TabsTab1NestedComponent { } export class TabsTab1NestedComponent {
id = '';
constructor(
private route: ActivatedRoute,
) {}
ngOnInit() {
this.id = this.route.snapshot.paramMap.get('id');
}
next() {
return parseInt(this.id, 10) + 1;
}
}

View File

@ -1,13 +1,13 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title>Tab 1 - Page 1</ion-title> <ion-title>{{title}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding> <ion-content padding>
<h1>Welcome to Tab1</h1> <h1>Welcome to Tab1</h1>
<p> <p>
<ion-button routerLink="/tabs/account/nested/12" id="goto-tab1-page2">Go to Page 2</ion-button> <ion-button routerLink="/tabs/account/nested/1" id="goto-tab1-page2">Go to Page 2</ion-button>
<ion-button routerLink="/tabs/lazy/nested" id="goto-tab3-page2">Go to Tab 3 - Page 2</ion-button> <ion-button routerLink="/tabs/lazy/nested" id="goto-tab3-page2">Go to Tab 3 - Page 2</ion-button>
<ion-button routerLink="/nested-outlet/page" id="goto-nested-page1">Go to nested</ion-button> <ion-button routerLink="/nested-outlet/page" id="goto-nested-page1">Go to nested</ion-button>
</p> </p>

View File

@ -1,7 +1,17 @@
import { Component } from '@angular/core'; import { Component, NgZone } from '@angular/core';
@Component({ @Component({
selector: 'app-tabs-tab1', selector: 'app-tabs-tab1',
templateUrl: './tabs-tab1.component.html', templateUrl: './tabs-tab1.component.html',
}) })
export class TabsTab1Component { } export class TabsTab1Component {
title = 'ERROR';
ionViewWillEnter() {
NgZone.assertInAngularZone();
setTimeout(() => {
NgZone.assertInAngularZone();
this.title = 'Tab 1 - Page 1';
});
}
}

View File

@ -1,6 +1,6 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title>Tab 2 - Page 1</ion-title> <ion-title>{{title}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
@ -8,7 +8,7 @@
<h1>Welcome to Tab 2</h1> <h1>Welcome to Tab 2</h1>
<p> <p>
<ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button> <ion-button routerLink="/tabs/account" id="goto-tab1-page1">Go to Tab 1 - Page 1</ion-button>
<ion-button routerLink="/tabs/account/nested/12" id="goto-tab1-page2">Go to Tab 1 - Page 2</ion-button> <ion-button routerLink="/tabs/account/nested/1" id="goto-tab1-page2">Go to Tab 1 - Page 2</ion-button>
<ion-button routerLink="/nested-outlet/page" id="goto-nested-page1">Go to nested</ion-button> <ion-button routerLink="/nested-outlet/page" id="goto-nested-page1">Go to nested</ion-button>
</p> </p>
</ion-content> </ion-content>

View File

@ -1,7 +1,17 @@
import { Component } from '@angular/core'; import { Component, NgZone } from '@angular/core';
@Component({ @Component({
selector: 'app-tabs-tab2', selector: 'app-tabs-tab2',
templateUrl: './tabs-tab2.component.html', templateUrl: './tabs-tab2.component.html',
}) })
export class TabsTab2Component { } export class TabsTab2Component {
title = 'ERROR';
ngOnInit() {
NgZone.assertInAngularZone();
setTimeout(() => {
NgZone.assertInAngularZone();
this.title = 'Tab 2 - Page 1';
});
}
}

View File

@ -0,0 +1,27 @@
import { IonicModule } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { TabsPageRoutingModule } from './tabs.router.module';
import { TabsComponent } from './tabs.component';
import { TabsTab1Component } from '../tabs-tab1/tabs-tab1.component';
import { TabsTab2Component } from '../tabs-tab2/tabs-tab2.component';
import { TabsTab1NestedComponent } from '../tabs-tab1-nested/tabs-tab1-nested.component';
@NgModule({
imports: [
IonicModule,
CommonModule,
FormsModule,
TabsPageRoutingModule
],
declarations: [
TabsComponent,
TabsTab1Component,
TabsTab2Component,
TabsTab1NestedComponent
]
})
export class TabsPageModule {}

View File

@ -0,0 +1,52 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TabsComponent } from './tabs.component';
import { TabsTab1NestedComponent } from '../tabs-tab1-nested/tabs-tab1-nested.component';
import { TabsTab1Component } from '../tabs-tab1/tabs-tab1.component';
import { TabsTab2Component } from '../tabs-tab2/tabs-tab2.component';
const routes: Routes = [
{
path: '',
component: TabsComponent,
children: [
{
path: 'account',
children: [
{
path: 'nested/:id',
component: TabsTab1NestedComponent
},
{
path: '',
component: TabsTab1Component
}
]
},
{
path: 'contact',
children: [
{
path: 'one',
component: TabsTab2Component
},
{
path: '',
redirectTo: 'one',
pathMatch: 'full'
}
]
},
{
path: 'lazy',
loadChildren: '../tabs-lazy/tabs-lazy.module#TabsLazyModule'
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class TabsPageRoutingModule {}

View File

@ -7,11 +7,11 @@ import { IonTabs, IonButton, IonSlides, IonSlide } from '@ionic/angular';
}) })
export class ViewChildComponent implements AfterViewInit { export class ViewChildComponent implements AfterViewInit {
@ViewChild(IonSlides) slides: IonSlides; @ViewChild(IonSlides, {static: true}) slides: IonSlides;
@ViewChild(IonButton) button: IonButton; @ViewChild(IonButton, {static: true}) button: IonButton;
@ViewChild(IonTabs) tabs: IonTabs; @ViewChild(IonTabs, {static: true}) tabs: IonTabs;
@ViewChild('div') div: ElementRef; @ViewChild('div', {static: true}) div: ElementRef;
@ViewChild('slide') slide: IonSlide; @ViewChild('slide', {static: true}) slide: IonSlide;
ngAfterViewInit() { ngAfterViewInit() {
const loaded = !!(this.slides && this.button && this.tabs && this.div && this.slide); const loaded = !!(this.slides && this.button && this.tabs && this.div && this.slide);

View File

@ -8,7 +8,7 @@ import { IonVirtualScroll } from '@ionic/angular';
}) })
export class VirtualScrollComponent { export class VirtualScrollComponent {
@ViewChild(IonVirtualScroll) virtualScroll: IonVirtualScroll; @ViewChild(IonVirtualScroll, {static: true}) virtualScroll: IonVirtualScroll;
items = Array.from({length: 100}, (_, i) => ({ name: `${i}`, checked: true})); items = Array.from({length: 100}, (_, i) => ({ name: `${i}`, checked: true}));