mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 19:57:22 +08:00
feature(routing): create external router controller for reconciling state from router in re-useable fashion
* external router controller * external router controller * gif it's working
This commit is contained in:
530
packages/angular/package-lock.json
generated
530
packages/angular/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -47,7 +47,7 @@
|
|||||||
"glob": "7.1.2",
|
"glob": "7.1.2",
|
||||||
"ionicons": "~3.0.0",
|
"ionicons": "~3.0.0",
|
||||||
"rxjs": "5.5.2",
|
"rxjs": "5.5.2",
|
||||||
"typescript": "~2.5.2",
|
"typescript": "latest",
|
||||||
"zone.js": "0.8.18"
|
"zone.js": "0.8.18"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -28,6 +28,7 @@ import { OutletInjector } from './outlet-injector';
|
|||||||
import { RouteEventHandler } from './route-event-handler';
|
import { RouteEventHandler } from './route-event-handler';
|
||||||
|
|
||||||
import { AngularComponentMounter, AngularEscapeHatch } from '..';
|
import { AngularComponentMounter, AngularEscapeHatch } from '..';
|
||||||
|
import { ensureExternalRounterController } from '../util/util';
|
||||||
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activateWith(activatedRoute: ActivatedRoute, cfr: ComponentFactoryResolver): Promise<void> {
|
activateWith(activatedRoute: ActivatedRoute, cfr: ComponentFactoryResolver): Promise<void> {
|
||||||
this.routeEventHandler.externalNavStart();
|
|
||||||
if (this.activationStatus !== NOT_ACTIVATED) {
|
if (this.activationStatus !== NOT_ACTIVATED) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@ -122,112 +123,24 @@ export class RouterOutlet implements OnDestroy, OnInit, RouterDelegate {
|
|||||||
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
|
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
|
||||||
|
|
||||||
const isTopLevel = !hasChildComponent(activatedRoute);
|
const isTopLevel = !hasChildComponent(activatedRoute);
|
||||||
|
|
||||||
|
return this.routeEventHandler.externalNavStart().then(() => {
|
||||||
return activateRoute(this.elementRef.nativeElement, component, cfr, injector, isTopLevel).then(() => {
|
return activateRoute(this.elementRef.nativeElement, component, cfr, injector, isTopLevel).then(() => {
|
||||||
this.changeDetector.markForCheck();
|
this.changeDetector.markForCheck();
|
||||||
this.activateEvents.emit(null);
|
this.activateEvents.emit(null);
|
||||||
this.activationStatus = ACTIVATED;
|
this.activationStatus = ACTIVATED;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function activateRoute(navElement: HTMLIonNavElement,
|
export function activateRoute(navElement: HTMLIonNavElement,
|
||||||
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector, isTopLevel: boolean): Promise<void> {
|
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector, isTopLevel: boolean): Promise<void> {
|
||||||
|
|
||||||
return navElement.componentOnReady().then(() => {
|
return ensureExternalRounterController().then((externalRouterController) => {
|
||||||
|
|
||||||
// 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, isTopLevel);
|
|
||||||
} else {
|
|
||||||
return updateNav(navElement, component, cfr, injector, isTopLevel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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.componentOnReady());
|
|
||||||
promises.push(tabElement.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, isTopLevel: boolean) {
|
|
||||||
|
|
||||||
const tab = navElement.parentElement as HTMLIonTabElement;
|
|
||||||
// tab.externalNav = true;
|
|
||||||
|
|
||||||
// (tab.parentElement as HTMLIonTabsElement).externalInitialize = true;
|
|
||||||
// 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;
|
|
||||||
// tabs.externalInitialize = true;
|
|
||||||
return isTabSelected(tabs, tab).then((isSelected: boolean) => {
|
|
||||||
if (!isSelected) {
|
|
||||||
const promise = updateNav(navElement, component, cfr, injector, isTopLevel);
|
|
||||||
(window as any).externalNavPromise = promise
|
|
||||||
// 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 promise.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, isTopLevel);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNav(navElement: HTMLIonNavElement,
|
|
||||||
component: Type<any>, cfr: ComponentFactoryResolver, injector: Injector, isTopLevel: boolean): Promise<NavResult> {
|
|
||||||
|
|
||||||
const escapeHatch = getEscapeHatch(cfr, injector);
|
const escapeHatch = getEscapeHatch(cfr, injector);
|
||||||
|
return externalRouterController.reconcileNav(navElement, component, escapeHatch, isTopLevel);
|
||||||
// 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.setRoot(component, {}, {}, escapeHatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(null, escapeHatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, null, escapeHatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's the top level nav, and it's not one of those other behaviors, so do a push so the user gets a chill animation
|
|
||||||
return navElement.push(component, {}, { animate: isTopLevel }, escapeHatch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NOT_ACTIVATED = 0;
|
export const NOT_ACTIVATED = 0;
|
||||||
|
@ -2,26 +2,29 @@ import { Injectable } from '@angular/core';
|
|||||||
import {
|
import {
|
||||||
Event,
|
Event,
|
||||||
NavigationEnd,
|
NavigationEnd,
|
||||||
NavigationStart,
|
|
||||||
Router
|
Router
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
let initialized = false;
|
import { ensureExternalRounterController } from '../util/util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RouteEventHandler {
|
export class RouteEventHandler {
|
||||||
|
|
||||||
constructor(private router: Router) {
|
constructor(private router: Router) {
|
||||||
(window as any).externalNav = false;
|
|
||||||
|
|
||||||
router.events.subscribe((event: Event) => {
|
router.events.subscribe((event: Event) => {
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
(window as any).externalNav = false;
|
ensureExternalRounterController().then((element) => {
|
||||||
|
element.updateExternalNavOccuring(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNavStart() {
|
externalNavStart() {
|
||||||
(window as any).externalNav = true;
|
return ensureExternalRounterController().then((element) => {
|
||||||
|
element.updateExternalNavOccuring(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,3 +25,13 @@ export function removeAllNodeChildren(element: HTMLElement) {
|
|||||||
export function isString(something: any) {
|
export function isString(something: any) {
|
||||||
return typeof something === 'string' ? true : false;
|
return typeof something === 'string' ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensureExternalRounterController(): Promise<HTMLIonExternalRouterControllerElement> {
|
||||||
|
const element = document.querySelector('ion-external-router-controller');
|
||||||
|
if (element) {
|
||||||
|
return (element as any).componentOnReady();
|
||||||
|
}
|
||||||
|
const toCreate = document.createElement('ion-external-router-controller');
|
||||||
|
document.body.appendChild(toCreate);
|
||||||
|
return (toCreate as any).componentOnReady();
|
||||||
|
}
|
30
packages/core/src/components.d.ts
vendored
30
packages/core/src/components.d.ts
vendored
@ -874,6 +874,36 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
ExternalRouterController as IonExternalRouterController
|
||||||
|
} from './components/external-router-controller/external-router-controller';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLIonExternalRouterControllerElement extends IonExternalRouterController, HTMLStencilElement {
|
||||||
|
}
|
||||||
|
var HTMLIonExternalRouterControllerElement: {
|
||||||
|
prototype: HTMLIonExternalRouterControllerElement;
|
||||||
|
new (): HTMLIonExternalRouterControllerElement;
|
||||||
|
};
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ion-external-router-controller": HTMLIonExternalRouterControllerElement;
|
||||||
|
}
|
||||||
|
interface ElementTagNameMap {
|
||||||
|
"ion-external-router-controller": HTMLIonExternalRouterControllerElement;
|
||||||
|
}
|
||||||
|
namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
"ion-external-router-controller": JSXElements.IonExternalRouterControllerAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace JSXElements {
|
||||||
|
export interface IonExternalRouterControllerAttributes extends HTMLAttributes {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FabButton as IonFabButton
|
FabButton as IonFabButton
|
||||||
} from './components/fab-button/fab-button';
|
} from './components/fab-button/fab-button';
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
import { Component, Method } from '@stencil/core';
|
||||||
|
import { EscapeHatch, NavResult } from '../../index';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
tag: 'ion-external-router-controller'
|
||||||
|
})
|
||||||
|
export class ExternalRouterController {
|
||||||
|
|
||||||
|
externalNavPromise: void | Promise<any> = null;
|
||||||
|
externalNavOccuring = false;
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
getExternalNavPromise(): void | Promise<any> {
|
||||||
|
return this.externalNavPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
clearExternalNavPromise(): void {
|
||||||
|
this.externalNavPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
getExternalNavOccuring(): boolean {
|
||||||
|
return this.externalNavOccuring;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
updateExternalNavOccuring(status: boolean) {
|
||||||
|
this.externalNavOccuring = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Method()
|
||||||
|
reconcileNav(nav: HTMLIonNavElement, component: any, escapeHatch: EscapeHatch, isTopLevel: boolean) {
|
||||||
|
return nav.componentOnReady().then(() => {
|
||||||
|
// check if the nav has an `<ion-tab>` as a parent
|
||||||
|
if (isParentTab(nav)) {
|
||||||
|
// check if the tab is selected
|
||||||
|
return updateTab(this, nav, component, escapeHatch, isTopLevel);
|
||||||
|
} else {
|
||||||
|
return updateNav(nav, component, escapeHatch, isTopLevel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isParentTab(navElement: HTMLIonNavElement) {
|
||||||
|
return navElement.parentElement.tagName.toLowerCase() === 'ion-tab';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTab(externalRouterController: ExternalRouterController, navElement: HTMLIonNavElement, component: any, escapeHatch: EscapeHatch, isTopLevel: boolean) {
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const promise = updateNav(navElement, component, escapeHatch, isTopLevel);
|
||||||
|
externalRouterController.externalNavPromise = promise;
|
||||||
|
// 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 promise.then(() => {
|
||||||
|
return tabs.select(tab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// okay cool, the tab is already selected, so we want to see a transition
|
||||||
|
return updateNav(navElement, component, escapeHatch, isTopLevel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTabSelected(tabsElement: HTMLIonTabsElement, tabElement: HTMLIonTabElement ): Promise<boolean> {
|
||||||
|
const promises: Promise<any>[] = [];
|
||||||
|
promises.push(tabsElement.componentOnReady());
|
||||||
|
promises.push(tabElement.componentOnReady());
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
return tabsElement.getSelected() === tabElement;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNav(navElement: HTMLIonNavElement,
|
||||||
|
component: any, escapeHatch: EscapeHatch, isTopLevel: boolean): Promise<NavResult> {
|
||||||
|
|
||||||
|
|
||||||
|
// 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.setRoot(component, {}, {}, escapeHatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(null, escapeHatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, null, escapeHatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's the top level nav, and it's not one of those other behaviors, so do a push so the user gets a chill animation
|
||||||
|
return navElement.push(component, {}, { animate: isTopLevel }, escapeHatch);
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
# ion-external-router-controller
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Auto Generated Below -->
|
||||||
|
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
#### clearExternalNavPromise()
|
||||||
|
|
||||||
|
|
||||||
|
#### getExternalNavOccuring()
|
||||||
|
|
||||||
|
|
||||||
|
#### getExternalNavPromise()
|
||||||
|
|
||||||
|
|
||||||
|
#### reconcileNav()
|
||||||
|
|
||||||
|
|
||||||
|
#### updateExternalNavOccuring()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
*Built with [StencilJS](https://stenciljs.com/)*
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
import { Component, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||||
import { getNavAsChildIfExists } from '../../utils/helpers';
|
import { ensureExternalRounterController, getNavAsChildIfExists } from '../../utils/helpers';
|
||||||
import { FrameworkDelegate } from '../..';
|
import { FrameworkDelegate } from '../..';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -85,11 +85,14 @@ export class Tab {
|
|||||||
const nav = getNavAsChildIfExists(this.el);
|
const nav = getNavAsChildIfExists(this.el);
|
||||||
if (nav) {
|
if (nav) {
|
||||||
// the tab's nav has been initialized externally
|
// the tab's nav has been initialized externally
|
||||||
if ((window as any).externalNavPromise) {
|
|
||||||
return (window as any).externalNavPromise.then(() => {
|
return ensureExternalRounterController().then((externalRouterController) => {
|
||||||
(window as any).externalNavPromise = null;
|
if (externalRouterController.getExternalNavPromise()) {
|
||||||
|
return (externalRouterController.getExternalNavPromise() as Promise<any>).then(() => {
|
||||||
|
externalRouterController.clearExternalNavPromise();
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
|
|
||||||
// the tab's nav has not been initialized externally, so
|
// the tab's nav has not been initialized externally, so
|
||||||
// check if we need to initiailize it
|
// check if we need to initiailize it
|
||||||
return (nav as any).componentOnReady().then(() => {
|
return (nav as any).componentOnReady().then(() => {
|
||||||
@ -100,7 +103,7 @@ export class Tab {
|
|||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
|
import { Component, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core';
|
||||||
import { Config, NavEventDetail, NavOutlet } from '../../index';
|
import { Config, NavEventDetail, NavOutlet } from '../../index';
|
||||||
|
|
||||||
|
import { ensureExternalRounterController } from '../../utils/helpers';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-tabs',
|
tag: 'ion-tabs',
|
||||||
@ -71,8 +73,13 @@ export class Tabs implements NavOutlet {
|
|||||||
this.loadConfig('tabsLayout', 'icon-top');
|
this.loadConfig('tabsLayout', 'icon-top');
|
||||||
this.loadConfig('tabsHighlight', true);
|
this.loadConfig('tabsHighlight', true);
|
||||||
|
|
||||||
return this.initTabs().then(() => {
|
const promises: Promise<any>[] = [];
|
||||||
if (! (window as any).externalNav) {
|
promises.push(this.initTabs());
|
||||||
|
promises.push(ensureExternalRounterController());
|
||||||
|
return Promise.all(promises).then(([_, externalRouterController]) => {
|
||||||
|
return (externalRouterController as HTMLIonExternalRouterControllerElement).getExternalNavOccuring();
|
||||||
|
}).then((externalNavOccuring) => {
|
||||||
|
if (!externalNavOccuring) {
|
||||||
return this.initSelect();
|
return this.initSelect();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -316,3 +316,13 @@ export function normalizeUrl(url: string) {
|
|||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensureExternalRounterController(): Promise<HTMLIonExternalRouterControllerElement> {
|
||||||
|
const element = document.querySelector('ion-external-router-controller');
|
||||||
|
if (element) {
|
||||||
|
return (element as any).componentOnReady();
|
||||||
|
}
|
||||||
|
const toCreate = document.createElement('ion-external-router-controller');
|
||||||
|
document.body.appendChild(toCreate);
|
||||||
|
return (toCreate as any).componentOnReady();
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user