fix(angular): router-outlet animation

This commit is contained in:
Manu Mtz.-Almeida
2018-03-21 19:34:22 +01:00
parent f0a40fabb9
commit 943e2f73c0
15 changed files with 315 additions and 67 deletions

View File

@ -1,30 +1,32 @@
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, OnInit, Output, ViewContainerRef } from '@angular/core';
import { ActivatedRoute, ChildrenOutletContexts } from '@angular/router';
import * as ctrl from './router-controller';
import { runTransition } from './router-transition';
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, OnInit, Output, ViewContainerRef, ElementRef } from '@angular/core';
import { ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET } from '@angular/router';
@Directive({selector: 'ion-router-outlet', exportAs: 'ionOutlet'})
@Directive({
selector: 'ion-router-outlet',
exportAs: 'outlet'
})
export class IonRouterOutlet implements OnDestroy, OnInit {
private activated: ComponentRef<any>|null = null;
private deactivated: ComponentRef<any>|null = null;
private _activatedRoute: ActivatedRoute|null = null;
private name: string;
private views: ctrl.RouteView[] = [];
@Output('activate') activateEvents = new EventEmitter<any>();
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
constructor(
private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
private resolver: ComponentFactoryResolver, @Attribute('name') name: string,
private resolver: ComponentFactoryResolver,
private elementRef: ElementRef,
@Attribute('name') name: string,
private changeDetector: ChangeDetectorRef) {
this.name = name || 'primary';
this.name = name || PRIMARY_OUTLET;
parentContexts.onChildOutletCreated(this.name, this as any);
}
ngOnDestroy(): void {
ctrl.destoryViews(this.views);
this.parentContexts.onChildOutletDestroyed(this.name);
}
@ -82,69 +84,68 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) {
this.activated = ref;
this._activatedRoute = activatedRoute;
ctrl.attachView(this.views, this.location, ref, activatedRoute);
this.location.insert(ref.hostView);
}
deactivate(): void {
if (this.activated) {
const c = this.component;
ctrl.deactivateView(this.views, this.activated);
this.deactivated = this.activated;
this.activated = null;
this._activatedRoute = null;
this.deactivateEvents.emit(c);
}
}
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
async activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
if (this.isActivated) {
throw new Error('Cannot activate an already activated outlet');
}
this._activatedRoute = activatedRoute;
const snapshot = (activatedRoute as any)._futureSnapshot;
const existingView = ctrl.getExistingView(this.views, activatedRoute);
if (existingView) {
// we've already got a view hanging around
this.activated = existingView.ref;
const component = <any>snapshot.routeConfig !.component;
resolver = resolver || this.resolver;
} else {
// haven't created this view yet
const snapshot = (activatedRoute as any)._futureSnapshot;
const factory = resolver.resolveComponentFactory(component);
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
const component = <any>snapshot.routeConfig !.component;
resolver = resolver || this.resolver;
const factory = resolver.resolveComponentFactory(component);
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
this.activated = this.location.createComponent(factory, this.location.length, injector);
// keep a ref
ctrl.initRouteViewElm(this.views, this.activated, activatedRoute);
}
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
this.activated = this.location.createComponent(factory, this.location.length, injector);
// Calling `markForCheck` to make sure we will run the change detection when the
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
this.changeDetector.markForCheck();
await this.transition();
this.activateEvents.emit(this.activated.instance);
}
const lastDeactivatedRef = ctrl.getLastDeactivatedRef(this.views);
async transition() {
const enteringRef = this.activated;
const enteringEl = (enteringRef && enteringRef.location && enteringRef.location.nativeElement) as HTMLElement;
if (enteringEl) {
enteringEl.classList.add('ion-page', 'hide-page');
runTransition(this.activated, lastDeactivatedRef).then(() => {
console.log('transition end');
this.activateEvents.emit(this.activated.instance);
});
const navEl = this.elementRef.nativeElement;
navEl.appendChild(enteringEl);
await navEl.componentOnReady();
await navEl.commit(enteringEl);
if (this.deactivated) {
this.deactivated.destroy();
this.deactivated = null;
}
}
}
}
class OutletInjector implements Injector {
constructor(
private route: ActivatedRoute, private childContexts: ChildrenOutletContexts,
private parent: Injector) {}
private route: ActivatedRoute,
private childContexts: ChildrenOutletContexts,
private parent: Injector
) {}
get(token: any, notFoundValue?: any): any {
if (token === ActivatedRoute) {

View File

@ -21,7 +21,7 @@ const routes: Routes = [
{ path: 'virtual-scroll', loadChildren: 'app/virtual-scroll/virtual-scroll.module#VirtualScrollModule' },
{ path: 'no-routing-nav', loadChildren: 'app/no-routing-nav/no-routing-nav.module#NoRoutingNavModule' },
{ path: 'simple-nav', loadChildren: 'app/simple-nav/simple-nav.module#SimpleNavModule' },
];
@NgModule({

View File

@ -5,49 +5,49 @@
<div>
<ul>
<li>
<a href='show-hide-when'>Show/Hide When Test Page</a>
<a [routerLink]="['/show-hide-when']">Show/Hide When Test Page</a>
</li>
<li>
<a href='basic-inputs'>Basic Inputs Test Page</a>
<a [routerLink]="['/basic-inputs']">Basic Inputs Test Page</a>
</li>
<li>
<a href='group-inputs'>Group Inputs Test Page</a>
<a [routerLink]="['/group-inputs']">Group Inputs Test Page</a>
</li>
<li>
<a href='form-sample'>Form Sample Test Page</a>
<a [routerLink]="['/form-sample']">Form Sample Test Page</a>
</li>
<li>
<a href='alert'>Alert Page</a>
<a [routerLink]="['/alert']">Alert Page</a>
</li>
<li>
<a href='badge'>Badge Page</a>
<a [routerLink]="['/badge']">Badge Page</a>
</li>
<li>
<a href='card'>Card Page</a>
<a [routerLink]="['/card']">Card Page</a>
</li>
<li>
<a href='content'>Content Page</a>
<a [routerLink]="['/content']">Content Page</a>
</li>
<li>
<a href='actionSheet'>Action Sheet Page</a>
<a [routerLink]="['/actionSheet']">Action Sheet Page</a>
</li>
<li>
<a href='toast'>Toast Page</a>
<a [routerLink]="['/toast']">Toast Page</a>
</li>
<li>
<a href='loading'>Loading Page</a>
<a [routerLink]="['/loading']">Loading Page</a>
</li>
<li>
<a href='modal'>Modal Page</a>
<a [routerLink]="['/modal']">Modal Page</a>
</li>
<li>
<a href='popover'>Popover Page</a>
<a [routerLink]="['/popover']">Popover Page</a>
</li>
<li>
<a href='segment'>Segment Page</a>
<a [routerLink]="['/segment']">Segment Page</a>
</li>
<li>
<a href='virtual-scroll'>Virtual Scroll Page</a>
<a [routerLink]="['/virtual-scroll']">Virtual Scroll Page</a>
</li>
</ul>
</div>
@ -61,12 +61,6 @@
<li>
<a href='simple-nav/page-one'>Simple Nav</a>
</li>
<li>
<a href='nested-nav/nested-page-one'>Nested Nav</a>
</li>
<li>
<a href='nav-then-tabs/login'>Nav Then Tabs</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageOne } from './page-one';
const routes: Routes = [
{ path: '', component: PageOne}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageOneRoutingModule { }

View File

@ -0,0 +1,17 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PageOne } from './page-one';
import { PageOneRoutingModule } from './page-one-routing.module';
@NgModule({
imports: [
CommonModule,
PageOneRoutingModule
],
declarations: [PageOne],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageOneModule { }

View File

@ -0,0 +1,28 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { PageTwo } from '../page-two/page-two';
@Component({
selector: 'page-one',
template: `
<ion-header>
<ion-toolbar>
<ion-title>Simple Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page One - {{ts}}
<div>
<ion-button [routerLink]="['/simple-nav/page-two']">Go to Page Two</ion-button>
</div>
</ion-content>
`
})
export class PageOne {
ts: number;
constructor() {
setInterval(() => {
this.ts = Date.now();
}, 500);
}
}

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageThree } from './page-three';
const routes: Routes = [
{ path: '', component: PageThree }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageThreeRoutingModule { }

View File

@ -0,0 +1,19 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PageThree } from './page-three';
import { PageThreeRoutingModule } from './page-three-routing.module';
@NgModule({
imports: [
CommonModule,
PageThreeRoutingModule
],
declarations: [
PageThree
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageThreeModule { }

View File

@ -0,0 +1,40 @@
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'page-three',
template: `
<ion-header>
<ion-toolbar>
<ion-title>Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page Three {{ts}}
<div>isProd: {{isProd}}</div>
<div>paramOne: {{paramOne}}</div>
<div>paramTwo: {{paramTwo}}</div>
<div>
<ion-button>Go Back</ion-button>
</div>
</ion-content>
`
})
export class PageThree {
ts: number;
isProd = false;
paramOne: any = null;
paramTwo: any = null;
// constructor(private navController: NavController, private navParams: NavParams) {
// this.isProd = navParams.get('isProd');
// this.paramOne = navParams.get('paramOne');
// this.paramTwo = navParams.get('paramTwo');
// setInterval(() => {
// this.ts = Date.now();
// }, 500);
// }
}

View File

@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PageTwo } from './page-two';
const routes: Routes = [
{
path: '',
component: PageTwo,
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PageTwoRoutingModule { }

View File

@ -0,0 +1,25 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
IonicAngularModule,
} from '@ionic/angular';
import { PageTwo } from './page-two';
import { PageTwoRoutingModule } from './page-two-routing.module';
@NgModule({
imports: [
CommonModule,
PageTwoRoutingModule,
IonicAngularModule,
],
declarations: [
PageTwo,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class PageTwoModule { }

View File

@ -0,0 +1,26 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { Location } from '@angular/common';
@Component({
selector: 'page-two',
template: `
<ion-header>
<ion-toolbar>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
Page Two - {{ts}}
<div>
<ion-button>Go to Page Three</ion-button>
</div>
<div>
<ion-button>Go Back</ion-button>
</div>
</ion-content>
`
})
export class PageTwo {
}

View File

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SimpleNavPageComponent } from './simple-nav.component';
const routes: Routes = [
{
path: '',
component: SimpleNavPageComponent,
children: [
{ path: 'page-one', loadChildren: './page-one/page-one.module#PageOneModule' },
{ path: 'page-two', loadChildren: './page-two/page-two.module#PageTwoModule' },
{ path: 'page-three/:paramOne/:paramTwo', loadChildren: './page-three/page-three.module#PageThreeModule', data: { isProd: true} }
]
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class SimpleNavRoutingModule { }

View File

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-nav-page',
template: `
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
`
})
export class SimpleNavPageComponent {}

View File

@ -0,0 +1,20 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { SimpleNavPageComponent } from './simple-nav.component';
import { SimpleNavRoutingModule } from './simple-nav-routing.module';
import { IonicAngularModule } from '@ionic/angular';
@NgModule({
declarations: [
SimpleNavPageComponent,
],
imports: [
CommonModule,
IonicAngularModule,
SimpleNavRoutingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class SimpleNavModule {}