mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 10:01:59 +08:00
refactor(nav): add initial support for url in general, add integration w/ ng-router
This commit is contained in:
3082
packages/angular/package-lock.json
generated
3082
packages/angular/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -33,14 +33,15 @@
|
||||
"dist/"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@angular/common": "next",
|
||||
"@angular/compiler": "next",
|
||||
"@angular/compiler-cli": "next",
|
||||
"@angular/core": "next",
|
||||
"@angular/forms": "next",
|
||||
"@angular/http": "next",
|
||||
"@angular/platform-browser": "next",
|
||||
"@angular/platform-browser-dynamic": "next",
|
||||
"@angular/common": "latest",
|
||||
"@angular/compiler": "latest",
|
||||
"@angular/compiler-cli": "latest",
|
||||
"@angular/core": "latest",
|
||||
"@angular/forms": "latest",
|
||||
"@angular/http": "latest",
|
||||
"@angular/platform-browser": "latest",
|
||||
"@angular/platform-browser-dynamic": "latest",
|
||||
"@danbucholtz/ng-router": "6.0.0-beta.1-20a6848be",
|
||||
"@ionic/core": "next",
|
||||
"glob": "7.1.2",
|
||||
"ionicons": "~3.0.0",
|
||||
|
@ -1,34 +0,0 @@
|
||||
import {
|
||||
ComponentFactoryResolver,
|
||||
Directive,
|
||||
ElementRef,
|
||||
Injector,
|
||||
Type,
|
||||
} from '@angular/core';
|
||||
|
||||
import { FrameworkDelegate } from '@ionic/core';
|
||||
|
||||
import { AngularComponentMounter } from '../providers/angular-component-mounter';
|
||||
import { AngularMountingData } from '../types/interfaces';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-nav',
|
||||
})
|
||||
export class IonNavDelegate implements FrameworkDelegate {
|
||||
|
||||
constructor(private elementRef: ElementRef, private angularComponentMounter: AngularComponentMounter, private componentResolveFactory: ComponentFactoryResolver, private injector: Injector) {
|
||||
this.elementRef.nativeElement.delegate = this;
|
||||
}
|
||||
|
||||
attachViewToDom(elementOrContainerToMountTo: HTMLIonNavElement, elementOrComponentToMount: Type<any>, _propsOrDataObj?: any, classesToAdd?: string[]): Promise<AngularMountingData> {
|
||||
|
||||
// wrap whatever the user provides in an ion-page
|
||||
return this.angularComponentMounter.attachViewToDom(elementOrContainerToMountTo, null, elementOrComponentToMount, this.componentResolveFactory, this.injector, _propsOrDataObj, classesToAdd);
|
||||
}
|
||||
|
||||
removeViewFromDom(parentElement: HTMLElement, childElement: HTMLElement) {
|
||||
return this.angularComponentMounter.removeViewFromDom(parentElement, childElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,6 @@ export const NavControllerToken = new InjectionToken<any>('NavControllerToken');
|
||||
export const NavParamsToken = new InjectionToken<any>('NavParamsToken');
|
||||
|
||||
export function getProviders(element: HTMLElement, data: any) {
|
||||
if (element.tagName !== 'ion-nav') {
|
||||
element.closest('ion-nav');
|
||||
}
|
||||
|
||||
const nearestNavElement = (element.tagName.toLowerCase() === 'ion-nav' ? element : element.closest('ion-nav')) as HTMLIonNavElement;
|
||||
|
||||
return [
|
||||
|
@ -1,14 +1,19 @@
|
||||
export { IonicAngularModule } from './module';
|
||||
|
||||
/* Components */
|
||||
export { IonNavDelegate } from './components/ion-nav';
|
||||
|
||||
/* Directives */
|
||||
export { MenuToggle } from './directives/menu-toggle';
|
||||
|
||||
/* Nav */
|
||||
export { IonNav } from './nav/ion-nav';
|
||||
export { AsyncActivateRoutes } from './nav/router/async-activated-routes';
|
||||
export { OutletInjector } from './nav/router/outlet-injector';
|
||||
export { ExtendedRouter } from './nav/router/router-extension';
|
||||
export { IonicRouterModule } from './nav/nav-module';
|
||||
|
||||
/* Providers */
|
||||
export { ActionSheetController, ActionSheetProxy } from './providers/action-sheet-controller';
|
||||
export { AlertController, AlertProxy } from './providers/alert-controller';
|
||||
export { AngularComponentMounter } from './providers/angular-component-mounter';
|
||||
export { App } from './providers/app';
|
||||
export { Events } from './providers/events';
|
||||
export { LoadingController, LoadingProxy } from './providers/loading-controller';
|
||||
@ -17,4 +22,6 @@ export { ModalController, ModalProxy } from './providers/modal-controller';
|
||||
export { NavController } from './providers/nav-controller';
|
||||
export { NavParams } from './providers/nav-params';
|
||||
export { PopoverController, PopoverProxy } from './providers/popover-controller';
|
||||
export { ToastController, ToastProxy } from './providers/toast-controller';
|
||||
export { ToastController, ToastProxy } from './providers/toast-controller';
|
||||
|
||||
export * from './types/interfaces';
|
@ -13,7 +13,7 @@ import { TextValueAccessor } from './control-value-accessors/text-value-accessor
|
||||
|
||||
|
||||
/* Components */
|
||||
import { IonNavDelegate } from './components/ion-nav';
|
||||
|
||||
|
||||
/* Directives */
|
||||
import { MenuToggle } from './directives/menu-toggle';
|
||||
@ -33,7 +33,6 @@ import { ToastController } from './providers/toast-controller';
|
||||
@NgModule({
|
||||
declarations: [
|
||||
BooleanValueAccessor,
|
||||
IonNavDelegate,
|
||||
MenuToggle,
|
||||
NumericValueAccessor,
|
||||
RadioValueAccessor,
|
||||
@ -42,7 +41,6 @@ import { ToastController } from './providers/toast-controller';
|
||||
],
|
||||
exports: [
|
||||
BooleanValueAccessor,
|
||||
IonNavDelegate,
|
||||
MenuToggle,
|
||||
NumericValueAccessor,
|
||||
RadioValueAccessor,
|
||||
|
239
packages/angular/src/nav/ion-nav.ts
Normal file
239
packages/angular/src/nav/ion-nav.ts
Normal file
@ -0,0 +1,239 @@
|
||||
import {
|
||||
Attribute,
|
||||
ChangeDetectorRef,
|
||||
ComponentFactoryResolver,
|
||||
ComponentRef,
|
||||
Directive,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Injector,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
Type,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
PRIMARY_OUTLET,
|
||||
ActivatedRoute,
|
||||
ChildrenOutletContexts,
|
||||
Router
|
||||
} from '@danbucholtz/ng-router';
|
||||
|
||||
|
||||
import { FrameworkDelegate } from '@ionic/core';
|
||||
|
||||
import { AngularComponentMounter, AngularEscapeHatch } from '..';
|
||||
import { OutletInjector } from './router/outlet-injector';
|
||||
|
||||
let id = 0;
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-nav',
|
||||
})
|
||||
export class IonNav implements FrameworkDelegate, OnDestroy, OnInit {
|
||||
|
||||
public name: string;
|
||||
public activationStatus = NOT_ACTIVATED;
|
||||
public componentConstructor: Type<any> = null;
|
||||
public componentInstance: any = null;
|
||||
public activatedRoute: ActivatedRoute = null;
|
||||
public activatedRouteData: any = {};
|
||||
public activeComponentRef: ComponentRef<any> = null;
|
||||
private id: number = id++;
|
||||
private parent: HTMLElement;
|
||||
|
||||
@Output('activate') activateEvents = new EventEmitter<any>();
|
||||
@Output('deactivate') deactivateEvents = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
public location: ViewContainerRef,
|
||||
public changeDetector: ChangeDetectorRef,
|
||||
public elementRef: ElementRef,
|
||||
protected angularComponentMounter: AngularComponentMounter,
|
||||
protected parentContexts: ChildrenOutletContexts,
|
||||
protected cfr: ComponentFactoryResolver,
|
||||
protected injector: Injector,
|
||||
@Attribute('name') name: string) {
|
||||
|
||||
this.parent = this.elementRef.nativeElement.parentElement;
|
||||
this.elementRef.nativeElement.delegate = this;
|
||||
this.name = name || PRIMARY_OUTLET;
|
||||
parentContexts.onChildOutletCreated(this.name, this as any);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
console.debug(`Nav ${this.id} ngOnDestroy`);
|
||||
this.parentContexts.onChildOutletDestroyed(this.name);
|
||||
}
|
||||
|
||||
get isActivated(): boolean {
|
||||
return this.activationStatus === ACTIVATION_IN_PROGRESS
|
||||
|| this.activationStatus === ACTIVATED;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.isActivated) {
|
||||
// If the outlet was not instantiated at the time the route got activated we need to populate
|
||||
// the outlet when it is initialized (ie inside a NgIf)
|
||||
const context = this.parentContexts.getContext(this.name);
|
||||
if (context && context.route) {
|
||||
// the component defined in the configuration is created
|
||||
// otherwise the component defined in the configuration is created
|
||||
this.activateWith(context.route, context.resolver || null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get component(): Object {
|
||||
return this.componentInstance;
|
||||
}
|
||||
|
||||
deactivate(): void {
|
||||
console.debug(`outlet ${this.id} is being deactivated`);
|
||||
this.activationStatus = NOT_ACTIVATED;
|
||||
this.deactivateEvents.emit(this.componentConstructor);
|
||||
}
|
||||
|
||||
activateWith(activatedRoute: ActivatedRoute, cfr: ComponentFactoryResolver): Promise<void> {
|
||||
if (this.activationStatus !== NOT_ACTIVATED) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.activationStatus = ACTIVATION_IN_PROGRESS;
|
||||
this.activatedRoute = activatedRoute;
|
||||
const snapshot = (activatedRoute as any)._futureSnapshot;
|
||||
const component = snapshot.routeConfig ? snapshot.routeConfig.component : null;
|
||||
cfr = cfr || this.cfr;
|
||||
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
|
||||
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
|
||||
|
||||
return activateRoute(this.elementRef.nativeElement, component, cfr, injector).then(() => {
|
||||
this.changeDetector.markForCheck();
|
||||
this.activateEvents.emit(null);
|
||||
this.activationStatus = ACTIVATED;
|
||||
});
|
||||
}
|
||||
|
||||
attachViewToDom(elementOrContainerToMountTo: HTMLIonNavElement,
|
||||
elementOrComponentToMount: Type<any>,
|
||||
data?: any,
|
||||
classesToAdd?: string[],
|
||||
escapeHatch: AngularEscapeHatch = {}): Promise<any> {
|
||||
|
||||
// wrap whatever the user provides in an ion-page
|
||||
const cfr = escapeHatch.cfr || this.cfr;
|
||||
const injector = escapeHatch.injector || this.injector;
|
||||
return this.angularComponentMounter.attachViewToDom(elementOrContainerToMountTo,
|
||||
null, elementOrComponentToMount, cfr, injector, data, classesToAdd);
|
||||
}
|
||||
|
||||
removeViewFromDom(parentElement: HTMLElement, childElement: HTMLElement) {
|
||||
return this.angularComponentMounter.removeViewFromDom(parentElement, childElement);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function activateRoute(navElement: HTMLIonNavElement,
|
||||
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector): Promise<void> {
|
||||
|
||||
return (navElement as any).componentOnReady().then(() => {
|
||||
|
||||
// check if the nav has an `<ion-tab>` as a parent
|
||||
if (isParentTab(navElement)) {
|
||||
// check if the tab is selected
|
||||
return updateTab(navElement, component, cfr, injector);
|
||||
} else {
|
||||
return updateNav(navElement, component, cfr, injector);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function isParentTab(navElement: HTMLIonNavElement) {
|
||||
return navElement.parentElement.tagName.toLowerCase() === 'ion-tab';
|
||||
}
|
||||
|
||||
function isTabSelected(tabsElement: HTMLIonTabsElement, tabElement: HTMLIonTabElement ): Promise<boolean> {
|
||||
const promises: Promise<any>[] = [];
|
||||
promises.push((tabsElement as any).componentOnReady());
|
||||
promises.push((tabElement as any).componentOnReady());
|
||||
return Promise.all(promises).then(() => {
|
||||
return tabsElement.getSelected() === tabElement;
|
||||
});
|
||||
}
|
||||
|
||||
function getSelected(tabsElement: HTMLIonTabsElement) {
|
||||
tabsElement.getSelected();
|
||||
}
|
||||
|
||||
function updateTab(navElement: HTMLIonNavElement,
|
||||
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector) {
|
||||
|
||||
const tab = navElement.parentElement as HTMLIonTabElement;
|
||||
// yeah yeah, I know this is kind of ugly but oh well, I know the internal structure of <ion-tabs>
|
||||
const tabs = tab.parentElement.parentElement as HTMLIonTabsElement;
|
||||
return isTabSelected(tabs, tab).then((isSelected: boolean) => {
|
||||
if (!isSelected) {
|
||||
// okay, the tab is not selected, so we need to do a "switch" transition
|
||||
// basically, we should update the nav, and then swap the tabs
|
||||
return updateNav(navElement, component, cfr, injector).then(() => {
|
||||
return tabs.select(tab);
|
||||
});
|
||||
}
|
||||
|
||||
// okay cool, the tab is already selected, so we want to see a transition
|
||||
return updateNav(navElement, component, cfr, injector);
|
||||
})
|
||||
}
|
||||
|
||||
function updateNav(navElement: HTMLIonNavElement,
|
||||
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector) {
|
||||
|
||||
// check if the component is the top view
|
||||
const activeViews = navElement.getViews();
|
||||
if (activeViews.length === 0) {
|
||||
// there isn't a view in the stack, so push one
|
||||
return navElement.push(component, {}, {}, {
|
||||
cfr,
|
||||
injector
|
||||
});
|
||||
}
|
||||
|
||||
const currentView = activeViews[activeViews.length - 1];
|
||||
if (currentView.component === component) {
|
||||
// the top view is already the component being activated, so there is no change needed
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// check if the component is the previous view, if so, pop back to it
|
||||
if (activeViews.length > 1) {
|
||||
// there's at least two views in the stack
|
||||
const previousView = activeViews[activeViews.length - 2];
|
||||
if (previousView.component === component) {
|
||||
// cool, we match the previous view, so pop it
|
||||
return navElement.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// check if the component is already in the stack of views, in which case we pop back to it
|
||||
for (const view of activeViews) {
|
||||
if (view.component === component) {
|
||||
// cool, we found the match, pop back to that bad boy
|
||||
return navElement.popTo(view);
|
||||
}
|
||||
}
|
||||
|
||||
// since it's none of those things, we should probably just push that bad boy and call it a day
|
||||
return navElement.push(component, {}, {}, {
|
||||
cfr,
|
||||
injector
|
||||
});
|
||||
}
|
||||
|
||||
export const NOT_ACTIVATED = 0;
|
||||
export const ACTIVATION_IN_PROGRESS = 1;
|
||||
export const ACTIVATED = 2;
|
110
packages/angular/src/nav/nav-module.ts
Normal file
110
packages/angular/src/nav/nav-module.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import {
|
||||
Location
|
||||
} from '@angular/common';
|
||||
|
||||
import {
|
||||
ApplicationRef,
|
||||
Compiler,
|
||||
Injector,
|
||||
ModuleWithProviders,
|
||||
NgModule,
|
||||
NgModuleFactoryLoader,
|
||||
Optional,
|
||||
} from '@angular/core';
|
||||
|
||||
import {ɵgetDOM as getDOM} from '@angular/platform-browser';
|
||||
|
||||
import {
|
||||
ROUTES,
|
||||
ROUTER_CONFIGURATION,
|
||||
ChildrenOutletContexts,
|
||||
ExtraOptions,
|
||||
Route,
|
||||
Router,
|
||||
RouteReuseStrategy,
|
||||
UrlHandlingStrategy,
|
||||
UrlSerializer
|
||||
} from '@danbucholtz/ng-router';
|
||||
|
||||
import { IonicAngularModule } from '../module';
|
||||
|
||||
import { PushPopOutletContexts } from './router/push-pop-outlet-contexts';
|
||||
import { ExtendedRouter } from './router/router-extension';
|
||||
import { IonNav } from './ion-nav';
|
||||
import { flatten } from '../util/util';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
IonNav
|
||||
],
|
||||
imports: [
|
||||
IonicAngularModule
|
||||
],
|
||||
exports: [
|
||||
IonNav
|
||||
]
|
||||
})
|
||||
export class IonicRouterModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: IonicRouterModule,
|
||||
providers: [
|
||||
{
|
||||
provide: ChildrenOutletContexts,
|
||||
useClass: PushPopOutletContexts
|
||||
},
|
||||
{
|
||||
provide: Router,
|
||||
useFactory: setupRouter,
|
||||
deps: [
|
||||
ApplicationRef, UrlSerializer, ChildrenOutletContexts, Location, Injector,
|
||||
NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION,
|
||||
[UrlHandlingStrategy, new Optional()], [RouteReuseStrategy, new Optional()]
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function setupRouter(
|
||||
ref: ApplicationRef, urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts,
|
||||
location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler,
|
||||
config: Route[][], opts: ExtraOptions = {}, urlHandlingStrategy?: UrlHandlingStrategy,
|
||||
routeReuseStrategy?: RouteReuseStrategy) {
|
||||
|
||||
const router = new ExtendedRouter(
|
||||
null, urlSerializer, contexts, location, injector, loader, compiler, flatten(config));
|
||||
|
||||
if (urlHandlingStrategy) {
|
||||
router.urlHandlingStrategy = urlHandlingStrategy;
|
||||
}
|
||||
|
||||
if (routeReuseStrategy) {
|
||||
router.routeReuseStrategy = routeReuseStrategy;
|
||||
}
|
||||
|
||||
if (opts.errorHandler) {
|
||||
router.errorHandler = opts.errorHandler;
|
||||
}
|
||||
|
||||
if (opts.enableTracing) {
|
||||
const dom = getDOM();
|
||||
router.events.subscribe(e => {
|
||||
dom.logGroup(`Router Event: ${(<any>e.constructor).name}`);
|
||||
dom.log(e.toString());
|
||||
dom.log(e);
|
||||
dom.logGroupEnd();
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.onSameUrlNavigation) {
|
||||
router.onSameUrlNavigation = opts.onSameUrlNavigation;
|
||||
}
|
||||
|
||||
if (opts.paramsInheritanceStrategy) {
|
||||
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
|
||||
}
|
||||
|
||||
return router;
|
||||
}
|
273
packages/angular/src/nav/router/async-activated-routes.ts
Normal file
273
packages/angular/src/nav/router/async-activated-routes.ts
Normal file
@ -0,0 +1,273 @@
|
||||
import {
|
||||
ComponentRef
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
ActivatedRouteSnapshot,
|
||||
ActivationEnd,
|
||||
ChildActivationEnd,
|
||||
ChildrenOutletContexts,
|
||||
Event,
|
||||
RouteReuseStrategy,
|
||||
RouterState,
|
||||
TreeNode,
|
||||
advanceActivatedRoute,
|
||||
forEach,
|
||||
nodeChildrenAsMap,
|
||||
} from '@danbucholtz/ng-router';
|
||||
|
||||
export class AsyncActivateRoutes {
|
||||
constructor(
|
||||
protected routeReuseStrategy: RouteReuseStrategy, protected futureState: RouterState,
|
||||
protected currState: RouterState, protected forwardEvent: (evt: Event) => void) {}
|
||||
|
||||
activate(parentContexts: ChildrenOutletContexts): void | Promise<void> {
|
||||
const futureRoot = (this.futureState as any)._root;
|
||||
const currRoot = this.currState ? (this.currState as any)._root : null;
|
||||
|
||||
const result = this.deactivateChildRoutes(futureRoot, currRoot, parentContexts);
|
||||
return Promise.resolve(result)
|
||||
.then(
|
||||
() => {
|
||||
advanceActivatedRoute(this.futureState.root);
|
||||
return this.activateChildRoutes(futureRoot, currRoot, parentContexts);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// De-activate the child route that are not re-used for the future state
|
||||
protected deactivateChildRoutes(
|
||||
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>|null,
|
||||
contexts: ChildrenOutletContexts): Promise<any> {
|
||||
|
||||
const children: {[outletName: string]: TreeNode<ActivatedRoute>} = nodeChildrenAsMap(currNode);
|
||||
|
||||
const promises = futureNode.children.map(futureChild => {
|
||||
const childOutletName = futureChild.value.outlet;
|
||||
const promise = this.deactivateRoutes(futureChild, children[childOutletName], contexts);
|
||||
promise
|
||||
.then(
|
||||
() => {
|
||||
delete children[childOutletName];
|
||||
}
|
||||
);
|
||||
return promise;
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(
|
||||
() => {
|
||||
const promises: Promise<void>[] = [];
|
||||
// De-activate the routes that will not be re-used
|
||||
forEach(children, (v: TreeNode<ActivatedRoute>, childName: string) => {
|
||||
promises.push(this.deactivateRouteAndItsChildren(v, contexts));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected deactivateRoutes(
|
||||
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
|
||||
parentContext: ChildrenOutletContexts): Promise<void> {
|
||||
const future = futureNode.value;
|
||||
const curr = currNode ? currNode.value : null;
|
||||
|
||||
if (future === curr) {
|
||||
// Reusing the node, check to see if the children need to be de-activated
|
||||
if (future.component) {
|
||||
// If we have a normal route, we need to go through an outlet.
|
||||
const context = parentContext.getContext(future.outlet);
|
||||
if (context) {
|
||||
return this.deactivateChildRoutes(futureNode, currNode, context.children);
|
||||
}
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||
return this.deactivateChildRoutes(futureNode, currNode, parentContext);
|
||||
}
|
||||
} else {
|
||||
if (curr) {
|
||||
// Deactivate the current route which will not be re-used
|
||||
return this.deactivateRouteAndItsChildren(currNode, parentContext);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
protected deactivateRouteAndItsChildren(
|
||||
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): Promise<void> {
|
||||
if (this.routeReuseStrategy.shouldDetach(route.value.snapshot)) {
|
||||
return this.detachAndStoreRouteSubtree(route, parentContexts);
|
||||
} else {
|
||||
return this.deactivateRouteAndOutlet(route, parentContexts);
|
||||
}
|
||||
}
|
||||
|
||||
protected detachAndStoreRouteSubtree(
|
||||
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): Promise<void> {
|
||||
const context = parentContexts.getContext(route.value.outlet);
|
||||
if (context && context.outlet) {
|
||||
const componentRefOrPromise = context.outlet.detach();
|
||||
return Promise.resolve(componentRefOrPromise)
|
||||
.then(
|
||||
(componentRef: ComponentRef<any>) => {
|
||||
const contexts = context.children.onOutletDeactivated();
|
||||
this.routeReuseStrategy.store(route.value.snapshot, {componentRef, route, contexts});
|
||||
}
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected deactivateRouteAndOutlet(
|
||||
route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): Promise<void> {
|
||||
const context = parentContexts.getContext(route.value.outlet);
|
||||
|
||||
if (context) {
|
||||
const children: {[outletName: string]: any} = nodeChildrenAsMap(route);
|
||||
const contexts = route.value.component ? context.children : parentContexts;
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
forEach(children, (v: any, k: string) => {
|
||||
promises.push(this.deactivateRouteAndItsChildren(v, contexts));
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(
|
||||
() => {
|
||||
if (context.outlet) {
|
||||
// Destroy the component
|
||||
const result = context.outlet.deactivate();
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
context.children.onOutletDeactivated();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected activateChildRoutes(
|
||||
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>|null,
|
||||
contexts: ChildrenOutletContexts): Promise<void> {
|
||||
|
||||
const children: {[outlet: string]: any} = nodeChildrenAsMap(currNode);
|
||||
|
||||
|
||||
const promises = futureNode.children.map(c => {
|
||||
const promise = this.activateRoutes(c, children[c.value.outlet], contexts);
|
||||
promise
|
||||
.then(
|
||||
() => {
|
||||
this.forwardEvent(new ActivationEnd(c.value.snapshot));
|
||||
}
|
||||
);
|
||||
return promise;
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(
|
||||
() => {
|
||||
if (futureNode.children.length) {
|
||||
this.forwardEvent(new ChildActivationEnd(futureNode.value.snapshot));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected activateRoutes(
|
||||
futureNode: TreeNode<ActivatedRoute>, currNode: TreeNode<ActivatedRoute>,
|
||||
parentContexts: ChildrenOutletContexts): Promise<void> {
|
||||
const future = futureNode.value;
|
||||
const curr = currNode ? currNode.value : null;
|
||||
|
||||
advanceActivatedRoute(future);
|
||||
|
||||
// reusing the node
|
||||
if (future === curr) {
|
||||
if (future.component) {
|
||||
// If we have a normal route, we need to go through an outlet.
|
||||
const context = parentContexts.getOrCreateContext(future.outlet);
|
||||
return this.activateChildRoutes(futureNode, currNode, context.children);
|
||||
} else {
|
||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||
return this.activateChildRoutes(futureNode, currNode, parentContexts);
|
||||
}
|
||||
} else {
|
||||
if (future.component) {
|
||||
// if we have a normal route, we need to place the component into the outlet and recurse.
|
||||
const context = parentContexts.getOrCreateContext(future.outlet);
|
||||
|
||||
if (this.routeReuseStrategy.shouldAttach(future.snapshot)) {
|
||||
const stored =
|
||||
(<any>this.routeReuseStrategy.retrieve(future.snapshot));
|
||||
this.routeReuseStrategy.store(future.snapshot, null);
|
||||
context.children.onOutletReAttached(stored.contexts);
|
||||
context.attachRef = stored.componentRef;
|
||||
context.route = stored.route.value;
|
||||
if (context.outlet) {
|
||||
// Attach right away when the outlet has already been instantiated
|
||||
// Otherwise attach from `RouterOutlet.ngOnInit` when it is instantiated
|
||||
const result = context.outlet.attach(stored.componentRef, stored.route.value);
|
||||
return Promise.resolve(result)
|
||||
.then(
|
||||
() => {
|
||||
return this.advanceActivatedRouteNodeAndItsChildren(stored.route);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve(this.advanceActivatedRouteNodeAndItsChildren(stored.route));
|
||||
|
||||
} else {
|
||||
const config = this.parentLoadedConfig(future.snapshot);
|
||||
const cmpFactoryResolver = config ? config.module.componentFactoryResolver : null;
|
||||
|
||||
context.route = future;
|
||||
context.resolver = cmpFactoryResolver;
|
||||
if (context.outlet) {
|
||||
// Activate the outlet when it has already been instantiated
|
||||
// Otherwise it will get activated from its `ngOnInit` when instantiated
|
||||
const result = context.outlet.activateWith(future, cmpFactoryResolver);
|
||||
return Promise.resolve(result)
|
||||
.then(
|
||||
() => {
|
||||
return this.activateChildRoutes(futureNode, null, context.children);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return this.activateChildRoutes(futureNode, null, context.children);
|
||||
}
|
||||
} else {
|
||||
// if we have a componentless route, we recurse but keep the same outlet map.
|
||||
return this.activateChildRoutes(futureNode, null, parentContexts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advanceActivatedRouteNodeAndItsChildren(node: TreeNode<ActivatedRoute>): void {
|
||||
advanceActivatedRoute(node.value);
|
||||
node.children.forEach(this.advanceActivatedRouteNodeAndItsChildren);
|
||||
}
|
||||
|
||||
parentLoadedConfig(snapshot: ActivatedRouteSnapshot): any|null {
|
||||
for (let s = snapshot.parent; s; s = s.parent) {
|
||||
const route = s.routeConfig as any;
|
||||
if (route && route._loadedConfig) return route._loadedConfig;
|
||||
if (route && route.component) return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
23
packages/angular/src/nav/router/outlet-injector.ts
Normal file
23
packages/angular/src/nav/router/outlet-injector.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Injector } from '@angular/core';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
ChildrenOutletContexts
|
||||
} from '@danbucholtz/ng-router';
|
||||
|
||||
export class OutletInjector implements Injector {
|
||||
|
||||
constructor(private route: ActivatedRoute, private childContexts: ChildrenOutletContexts, private parent: Injector) {
|
||||
}
|
||||
|
||||
get(token: any, notFoundValue?: any): any {
|
||||
if (token === ActivatedRoute) {
|
||||
return this.route;
|
||||
}
|
||||
|
||||
if (token === ChildrenOutletContexts) {
|
||||
return this.childContexts;
|
||||
}
|
||||
|
||||
return this.parent.get(token, notFoundValue);
|
||||
}
|
||||
}
|
41
packages/angular/src/nav/router/push-pop-outlet-contexts.ts
Normal file
41
packages/angular/src/nav/router/push-pop-outlet-contexts.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {
|
||||
ComponentFactoryResolver,
|
||||
ComponentRef
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
ChildrenOutletContexts,
|
||||
OutletContext,
|
||||
RouterOutlet
|
||||
} from '@danbucholtz/ng-router';
|
||||
|
||||
export class PushPopOutletContexts extends ChildrenOutletContexts {
|
||||
|
||||
// this method is a public api, but the members in the ChildrenOutletContexts are private
|
||||
// so we're gonna cheat, 'cause we play to win
|
||||
onOutletDeactivated(): Map<string, OutletContext> {
|
||||
return (this as any).contexts;
|
||||
}
|
||||
|
||||
getOrCreateContext(childName: string): OutletContext {
|
||||
let context = this.getContext(childName) as any;
|
||||
|
||||
if (!context) {
|
||||
context = {
|
||||
children: new PushPopOutletContexts()
|
||||
};
|
||||
(this as any).contexts.set(childName, context);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
export interface PushPopOutletContext {
|
||||
outlet?: RouterOutlet;
|
||||
route?: ActivatedRoute;
|
||||
resolver?: ComponentFactoryResolver;
|
||||
children?: PushPopOutletContexts;
|
||||
attachRef: ComponentRef<any>;
|
||||
}
|
226
packages/angular/src/nav/router/router-extension.ts
Normal file
226
packages/angular/src/nav/router/router-extension.ts
Normal file
@ -0,0 +1,226 @@
|
||||
|
||||
import {
|
||||
Event,
|
||||
GuardsCheckEnd,
|
||||
GuardsCheckStart,
|
||||
NavigationCancel,
|
||||
NavigationEnd,
|
||||
NavigationError,
|
||||
PreActivation,
|
||||
ResolveEnd,
|
||||
ResolveStart,
|
||||
Router,
|
||||
RouterState,
|
||||
RouterStateSnapshot,
|
||||
RoutesRecognized,
|
||||
UrlTree,
|
||||
applyRedirects,
|
||||
createRouterState,
|
||||
isNavigationCancelingError,
|
||||
recognize
|
||||
} from '@danbucholtz/ng-router';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
import { concatMap } from 'rxjs/operator/concatMap';
|
||||
import { map } from 'rxjs/operator/map';
|
||||
import { mergeMap } from 'rxjs/operator/mergeMap';
|
||||
|
||||
import { AsyncActivateRoutes } from './async-activated-routes';
|
||||
|
||||
|
||||
export class ExtendedRouter extends Router {
|
||||
|
||||
protected runNavigate(
|
||||
url: UrlTree, rawUrl: UrlTree, skipLocationChange: boolean, replaceUrl: boolean, id: number,
|
||||
precreatedState: RouterStateSnapshot|null): Promise<boolean> {
|
||||
if (id !== this.navigationId) {
|
||||
(this.events as Subject<Event>)
|
||||
.next(new NavigationCancel(
|
||||
id, this.serializeUrl(url),
|
||||
`Navigation ID ${id} is not equal to the current navigation id ${this.navigationId}`));
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return new Promise((resolvePromise, rejectPromise) => {
|
||||
// create an observable of the url and route state snapshot
|
||||
// this operation do not result in any side effects
|
||||
let urlAndSnapshot$: Observable<{appliedUrl: UrlTree, snapshot: RouterStateSnapshot}>;
|
||||
if (!precreatedState) {
|
||||
const moduleInjector = this.ngModule.injector;
|
||||
const redirectsApplied$ =
|
||||
applyRedirects(moduleInjector, this.configLoader, this.urlSerializer, url, this.config);
|
||||
|
||||
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
|
||||
return map.call(
|
||||
recognize(
|
||||
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl),
|
||||
this.paramsInheritanceStrategy),
|
||||
(snapshot: any) => {
|
||||
|
||||
(this.events as Subject<Event>)
|
||||
.next(new RoutesRecognized(
|
||||
id, this.serializeUrl(url), this.serializeUrl(appliedUrl), snapshot));
|
||||
|
||||
return {appliedUrl, snapshot};
|
||||
});
|
||||
});
|
||||
} else {
|
||||
urlAndSnapshot$ = of ({appliedUrl: url, snapshot: precreatedState});
|
||||
}
|
||||
|
||||
const beforePreactivationDone$ = mergeMap.call(
|
||||
urlAndSnapshot$, (p: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
||||
return map.call(this.hooks.beforePreactivation(p.snapshot), () => p);
|
||||
});
|
||||
|
||||
// run preactivation: guards and data resolvers
|
||||
let preActivation: PreActivation;
|
||||
|
||||
const preactivationSetup$ = map.call(
|
||||
beforePreactivationDone$,
|
||||
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
||||
const moduleInjector = this.ngModule.injector;
|
||||
preActivation = new PreActivation(
|
||||
snapshot, this.routerState.snapshot, moduleInjector,
|
||||
(evt: Event) => this.triggerEvent(evt));
|
||||
preActivation.initialize(this.rootContexts);
|
||||
return {appliedUrl, snapshot};
|
||||
});
|
||||
|
||||
const preactivationCheckGuards$ = mergeMap.call(
|
||||
preactivationSetup$,
|
||||
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
|
||||
if (this.navigationId !== id) return of (false);
|
||||
|
||||
this.triggerEvent(
|
||||
new GuardsCheckStart(id, this.serializeUrl(url), appliedUrl, snapshot));
|
||||
|
||||
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
|
||||
this.triggerEvent(new GuardsCheckEnd(
|
||||
id, this.serializeUrl(url), appliedUrl, snapshot, shouldActivate));
|
||||
return {appliedUrl: appliedUrl, snapshot: snapshot, shouldActivate: shouldActivate};
|
||||
});
|
||||
});
|
||||
|
||||
const preactivationResolveData$ = mergeMap.call(
|
||||
preactivationCheckGuards$,
|
||||
(p: {appliedUrl: string, snapshot: RouterStateSnapshot, shouldActivate: boolean}) => {
|
||||
if (this.navigationId !== id) return of (false);
|
||||
|
||||
if (p.shouldActivate && preActivation.isActivating()) {
|
||||
this.triggerEvent(
|
||||
new ResolveStart(id, this.serializeUrl(url), p.appliedUrl, p.snapshot));
|
||||
return map.call(preActivation.resolveData(this.paramsInheritanceStrategy), () => {
|
||||
this.triggerEvent(
|
||||
new ResolveEnd(id, this.serializeUrl(url), p.appliedUrl, p.snapshot));
|
||||
return p;
|
||||
});
|
||||
} else {
|
||||
return of (p);
|
||||
}
|
||||
});
|
||||
|
||||
const preactivationDone$ = mergeMap.call(preactivationResolveData$, (p: any) => {
|
||||
return map.call(this.hooks.afterPreactivation(p.snapshot), () => p);
|
||||
});
|
||||
|
||||
|
||||
// create router state
|
||||
// this operation has side effects => route state is being affected
|
||||
const routerState$ =
|
||||
map.call(preactivationDone$, ({appliedUrl, snapshot, shouldActivate}: any) => {
|
||||
if (shouldActivate) {
|
||||
const state = createRouterState(this.routeReuseStrategy, snapshot, this.routerState);
|
||||
return {appliedUrl, state, shouldActivate};
|
||||
} else {
|
||||
return {appliedUrl, state: null, shouldActivate};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// applied the new router state
|
||||
// this operation has side effects
|
||||
let navigationIsSuccessful = false;
|
||||
const storedState = this.routerState;
|
||||
const storedUrl = this.currentUrlTree;
|
||||
|
||||
const activatedRoutes: AsyncActivateRoutes[] = [];
|
||||
|
||||
routerState$
|
||||
.forEach(({appliedUrl, state, shouldActivate}: any) => {
|
||||
if (!shouldActivate || id !== this.navigationId) {
|
||||
navigationIsSuccessful = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentUrlTree = appliedUrl;
|
||||
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
|
||||
|
||||
(this as{routerState: RouterState}).routerState = state;
|
||||
|
||||
if (!skipLocationChange) {
|
||||
const path = this.urlSerializer.serialize(this.rawUrlTree);
|
||||
if (this.location.isCurrentPathEqualTo(path) || replaceUrl) {
|
||||
// this.location.replaceState(path, '', {navigationId: id});
|
||||
this.location.replaceState(path, '');
|
||||
} else {
|
||||
// this.location.go(path, '', {navigationId: id});
|
||||
this.location.go(path, '');
|
||||
}
|
||||
}
|
||||
|
||||
activatedRoutes.push(new AsyncActivateRoutes(this.routeReuseStrategy, state, storedState, (evt: Event) => this.triggerEvent(evt)))
|
||||
})
|
||||
.then(() => {
|
||||
const promises = activatedRoutes.map(activatedRoute => activatedRoute.activate(this.rootContexts));
|
||||
return Promise.all(promises)
|
||||
.then(
|
||||
() => {
|
||||
navigationIsSuccessful = true;
|
||||
}
|
||||
);
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
if (navigationIsSuccessful) {
|
||||
this.navigated = true;
|
||||
this.lastSuccessfulId = id;
|
||||
(this.events as Subject<Event>)
|
||||
.next(new NavigationEnd(
|
||||
id, this.serializeUrl(url), this.serializeUrl(this.currentUrlTree)));
|
||||
resolvePromise(true);
|
||||
} else {
|
||||
this.resetUrlToCurrentUrlTree();
|
||||
(this.events as Subject<Event>)
|
||||
.next(new NavigationCancel(id, this.serializeUrl(url), ''));
|
||||
resolvePromise(false);
|
||||
}
|
||||
},
|
||||
(e: any) => {
|
||||
if (isNavigationCancelingError(e)) {
|
||||
this.navigated = true;
|
||||
this.resetStateAndUrl(storedState, storedUrl, rawUrl);
|
||||
(this.events as Subject<Event>)
|
||||
.next(new NavigationCancel(id, this.serializeUrl(url), e.message));
|
||||
|
||||
resolvePromise(false);
|
||||
} else {
|
||||
this.resetStateAndUrl(storedState, storedUrl, rawUrl);
|
||||
(this.events as Subject<Event>)
|
||||
.next(new NavigationError(id, this.serializeUrl(url), e));
|
||||
try {
|
||||
resolvePromise(this.errorHandler(e));
|
||||
} catch (ee) {
|
||||
rejectPromise(ee);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
triggerEvent(e: Event): void { ((this.events as any)as Subject<Event>).next(e); }
|
||||
}
|
@ -26,7 +26,7 @@ export class AngularComponentMounter {
|
||||
|
||||
const crf = componentResolveFactory ? componentResolveFactory : this.defaultCfr;
|
||||
|
||||
const mountingData = attachViewToDom(crf, parentElement, hostElement, componentToMount, injector, this.appRef, data, classesToAdd);
|
||||
const mountingData = this.attachViewToDomImpl(crf, parentElement, hostElement, componentToMount, injector, this.appRef, data, classesToAdd);
|
||||
resolve(mountingData);
|
||||
});
|
||||
});
|
||||
@ -41,6 +41,40 @@ export class AngularComponentMounter {
|
||||
});
|
||||
}
|
||||
|
||||
attachViewToDomImpl(crf: ComponentFactoryResolver, parentElement: HTMLElement, hostElement: HTMLElement, componentToMount: Type<any>, injector: Injector, appRef: ApplicationRef, data: any, classesToAdd: string[]): AngularMountingData {
|
||||
|
||||
const componentProviders = ReflectiveInjector.resolve(getProviders(parentElement, data));
|
||||
const componentFactory = crf.resolveComponentFactory(componentToMount);
|
||||
if (!hostElement) {
|
||||
hostElement = document.createElement(componentFactory.selector);
|
||||
}
|
||||
|
||||
const childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, injector);
|
||||
const componentRef = componentFactory.create(childInjector, [], hostElement);
|
||||
for (const clazz of classesToAdd) {
|
||||
hostElement.classList.add(clazz);
|
||||
}
|
||||
|
||||
parentElement.appendChild(hostElement);
|
||||
|
||||
appRef.attachView(componentRef.hostView);
|
||||
|
||||
const mountingData = {
|
||||
component: componentToMount,
|
||||
componentFactory,
|
||||
childInjector,
|
||||
componentRef,
|
||||
instance: componentRef.instance,
|
||||
angularHostElement: componentRef.location.nativeElement,
|
||||
element: hostElement,
|
||||
data
|
||||
};
|
||||
|
||||
elementToComponentRefMap.set(hostElement, mountingData);
|
||||
|
||||
return mountingData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function removeViewFromDom(_parentElement: HTMLElement, childElement: HTMLElement) {
|
||||
@ -49,35 +83,3 @@ export function removeViewFromDom(_parentElement: HTMLElement, childElement: HTM
|
||||
mountingData.componentRef.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export function attachViewToDom(crf: ComponentFactoryResolver, parentElement: HTMLElement, hostElement: HTMLElement, componentToMount: Type<any>, injector: Injector, appRef: ApplicationRef, data: any, classesToAdd: string[]): AngularMountingData {
|
||||
|
||||
const componentProviders = ReflectiveInjector.resolve(getProviders(parentElement, data));
|
||||
const componentFactory = crf.resolveComponentFactory(componentToMount);
|
||||
if (!hostElement) {
|
||||
hostElement = document.createElement(componentFactory.selector);
|
||||
}
|
||||
|
||||
const childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, injector);
|
||||
const componentRef = componentFactory.create(childInjector, [], hostElement);
|
||||
for (const clazz of classesToAdd) {
|
||||
hostElement.classList.add(clazz);
|
||||
}
|
||||
|
||||
parentElement.appendChild(hostElement);
|
||||
|
||||
appRef.attachView(componentRef.hostView);
|
||||
|
||||
const mountingData = {
|
||||
componentFactory,
|
||||
childInjector,
|
||||
componentRef,
|
||||
instance: componentRef.instance,
|
||||
angularHostElement: componentRef.location.nativeElement,
|
||||
element: hostElement,
|
||||
};
|
||||
|
||||
elementToComponentRefMap.set(hostElement, mountingData);
|
||||
|
||||
return mountingData;
|
||||
}
|
||||
|
@ -36,9 +36,9 @@ export class ModalController implements FrameworkDelegate {
|
||||
});
|
||||
}
|
||||
|
||||
attachViewToDom(elementOrContainerToMountTo: HTMLElement, elementOrComponentToMount: Type<any>, _propsOrDataObj?: any, classesToAdd?: string[]): Promise<AngularMountingData> {
|
||||
attachViewToDom(elementOrContainerToMountTo: HTMLElement, elementOrComponentToMount: Type<any>, data?: any, classesToAdd?: string[]): Promise<AngularMountingData> {
|
||||
|
||||
return this.angularComponentMounter.attachViewToDom(elementOrContainerToMountTo, null, elementOrComponentToMount, this.componentResolveFactory, this.injector, _propsOrDataObj, classesToAdd);
|
||||
return this.angularComponentMounter.attachViewToDom(elementOrContainerToMountTo, null, elementOrComponentToMount, this.componentResolveFactory, this.injector, data, classesToAdd);
|
||||
}
|
||||
|
||||
removeViewFromDom(parentElement: HTMLElement, childElement: HTMLElement) {
|
||||
|
@ -29,9 +29,16 @@ export class PopoverController implements FrameworkDelegate {
|
||||
return getPopoverProxy(opts);
|
||||
}
|
||||
|
||||
attachViewToDom(elementOrContainerToMountTo: HTMLElement, elementOrComponentToMount: Type<any>, _propsOrDataObj?: any, classesToAdd?: string[]): Promise<AngularMountingData> {
|
||||
dismiss(data?: any, role?: string, id?: number) {
|
||||
const popoverController = document.querySelector('ion-popover-controller');
|
||||
return (popoverController as any).componentOnReady().then(() => {
|
||||
return popoverController.dismiss(data, role, id);
|
||||
});
|
||||
}
|
||||
|
||||
return this.angularComponentMounter.attachViewToDom(elementOrContainerToMountTo, null, elementOrComponentToMount, this.componentResolveFactory, this.injector, _propsOrDataObj, classesToAdd);
|
||||
attachViewToDom(elementOrContainerToMountTo: HTMLElement, elementOrComponentToMount: Type<any>, data?: any, classesToAdd?: string[]): Promise<AngularMountingData> {
|
||||
|
||||
return this.angularComponentMounter.attachViewToDom(elementOrContainerToMountTo, null, elementOrComponentToMount, this.componentResolveFactory, this.injector, data, classesToAdd);
|
||||
}
|
||||
|
||||
removeViewFromDom(parentElement: HTMLElement, childElement: HTMLElement) {
|
||||
|
@ -1,8 +1,10 @@
|
||||
import {
|
||||
ComponentFactory,
|
||||
ComponentFactoryResolver,
|
||||
ComponentRef,
|
||||
Injector
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute } from '@danbucholtz/ng-router';
|
||||
|
||||
import { FrameworkMountingData } from '@ionic/core';
|
||||
|
||||
@ -13,3 +15,9 @@ export interface AngularMountingData extends FrameworkMountingData {
|
||||
instance?: any;
|
||||
angularHostElement?: HTMLElement;
|
||||
}
|
||||
|
||||
export interface AngularEscapeHatch {
|
||||
activatedRoute?: ActivatedRoute;
|
||||
cfr?: ComponentFactoryResolver;
|
||||
injector?: Injector;
|
||||
}
|
@ -15,3 +15,20 @@ export function ensureElementInBody(elementName: string) {
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
export function removeAllNodeChildren(element: HTMLElement) {
|
||||
while (element.firstChild) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
export function isString(something: any) {
|
||||
return typeof something === 'string' ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens single-level nested arrays.
|
||||
*/
|
||||
export function flatten<T>(arr: T[][]): T[] {
|
||||
return Array.prototype.concat.apply([], arr);
|
||||
}
|
@ -10,8 +10,8 @@
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"outDir": "dist",
|
||||
"removeComments": false,
|
||||
"sourceMap": true,
|
||||
|
Reference in New Issue
Block a user