mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 10:01:59 +08:00
feat(angular): support binding routing data to component inputs (#27694)
Issue number: Resolves #27476 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Ionic Angular application on Angular v16 cannot use the [`bindToComponentInputs`](https://angular.io/api/router/ExtraOptions#bindToComponentInputs) feature to assign route parameters, query parameters, route data and route resolve data to component inputs. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Ionic Angular developers can use the option on `RouterModule.forRoot` to enable the Angular feature for binding the route snapshot data to the component inputs. **Modules** ```ts @NgModule({ imports: [ RouterModule.forRoot([/* your routes */], { bindToComponentInputs: true // <-- enable this feature }) ] }) export class AppModule { } ``` **Standalone** ```ts import { withComponentInputBinding } from '@angular/router'; bootstrapApplication(App, { providers: [ provideRouter(routes, //... other features withComponentInputBinding() // <-- enable this feature ) ], }); ``` With this feature enabled, developers can bind route parameters, query parameters, route data and the returned value from a resolver to input bindings on their component. For example, with a route configuration of: ```ts RouterModule.forChild([ { path: ':id', data: { title: 'Hello world' }, resolve: { name: () => 'Resolved name' }, loadComponent: () => import('./example-component/example.component').then(c => c.ExampleComponent) } ]) ``` and a component configuration of: ```ts @Component({ }) export class ExampleComponent { @Input() id?: string; @Input() title?: string; @Input() name?: string; @Input() query?: string; } ``` Navigating to the component with a url of: `/2?query=searchphrase` The following would occur: - `id` would return `2` - `title` would return `Hello world` - `name` would return `Resolved name` - `query` would return `searchphrase` ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> This PR will need to be targeted to a minor release once a design doc is approved by the team. Dev-build: `7.1.3-dev.11689276547.129acb40`
This commit is contained in:
@ -15,10 +15,14 @@ import {
|
||||
Output,
|
||||
SkipSelf,
|
||||
EnvironmentInjector,
|
||||
Input,
|
||||
InjectionToken,
|
||||
Injectable,
|
||||
reflectComponentType,
|
||||
} from '@angular/core';
|
||||
import { OutletContext, Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET, Data } from '@angular/router';
|
||||
import { componentOnReady } from '@ionic/core';
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
import { Observable, BehaviorSubject, Subscription, combineLatest, of } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { AnimationBuilder } from '../../ionic-core';
|
||||
@ -39,22 +43,28 @@ import { RouteView, getUrl } from './stack-utils';
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
nativeEl: HTMLIonRouterOutletElement;
|
||||
|
||||
private activated: ComponentRef<any> | null = null;
|
||||
activatedView: RouteView | null = null;
|
||||
tabsPrefix: string | undefined;
|
||||
|
||||
private _activatedRoute: ActivatedRoute | null = null;
|
||||
private _swipeGesture?: boolean;
|
||||
private name: string;
|
||||
private stackCtrl: StackController;
|
||||
|
||||
// Maintain map of activated route proxies for each component instance
|
||||
private proxyMap = new WeakMap<any, ActivatedRoute>();
|
||||
|
||||
// Keep the latest activated route in a subject for the proxy routes to switch map to
|
||||
private currentActivatedRoute$ = new BehaviorSubject<{ component: any; activatedRoute: ActivatedRoute } | null>(null);
|
||||
|
||||
tabsPrefix: string | undefined;
|
||||
private activated: ComponentRef<any> | null = null;
|
||||
/** @internal */
|
||||
get activatedComponentRef(): ComponentRef<any> | null {
|
||||
return this.activated;
|
||||
}
|
||||
private _activatedRoute: ActivatedRoute | null = null;
|
||||
|
||||
/**
|
||||
* The name of the outlet
|
||||
*/
|
||||
@Input() name = PRIMARY_OUTLET;
|
||||
|
||||
@Output() stackEvents = new EventEmitter<any>();
|
||||
// eslint-disable-next-line @angular-eslint/no-output-rename
|
||||
@ -65,6 +75,9 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
private parentContexts = inject(ChildrenOutletContexts);
|
||||
private location = inject(ViewContainerRef);
|
||||
private environmentInjector = inject(EnvironmentInjector);
|
||||
private inputBinder = inject(INPUT_BINDER, { optional: true });
|
||||
/** @nodoc */
|
||||
readonly supportsBindingToComponentInputs = true;
|
||||
|
||||
// Ionic providers
|
||||
private config = inject(Config);
|
||||
@ -109,6 +122,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.stackCtrl.destroy();
|
||||
this.inputBinder?.unsubscribeFromRouteData(this);
|
||||
}
|
||||
|
||||
getContext(): OutletContext | null {
|
||||
@ -275,6 +289,8 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||
this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
|
||||
}
|
||||
|
||||
this.inputBinder?.bindActivatedRouteToOutletComponent(this);
|
||||
|
||||
this.activatedView = enteringView;
|
||||
|
||||
/**
|
||||
@ -415,3 +431,79 @@ class OutletInjector implements Injector {
|
||||
return this.parent.get(token, notFoundValue);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: FW-4785 - Remove this once Angular 15 support is dropped
|
||||
export const INPUT_BINDER = new InjectionToken<RoutedComponentInputBinder>('');
|
||||
|
||||
/**
|
||||
* Injectable used as a tree-shakable provider for opting in to binding router data to component
|
||||
* inputs.
|
||||
*
|
||||
* The RouterOutlet registers itself with this service when an `ActivatedRoute` is attached or
|
||||
* activated. When this happens, the service subscribes to the `ActivatedRoute` observables (params,
|
||||
* queryParams, data) and sets the inputs of the component using `ComponentRef.setInput`.
|
||||
* Importantly, when an input does not have an item in the route data with a matching key, this
|
||||
* input is set to `undefined`. If it were not done this way, the previous information would be
|
||||
* retained if the data got removed from the route (i.e. if a query parameter is removed).
|
||||
*
|
||||
* The `RouterOutlet` should unregister itself when destroyed via `unsubscribeFromRouteData` so that
|
||||
* the subscriptions are cleaned up.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RoutedComponentInputBinder {
|
||||
private outletDataSubscriptions = new Map<IonRouterOutlet, Subscription>();
|
||||
|
||||
bindActivatedRouteToOutletComponent(outlet: IonRouterOutlet): void {
|
||||
this.unsubscribeFromRouteData(outlet);
|
||||
this.subscribeToRouteData(outlet);
|
||||
}
|
||||
|
||||
unsubscribeFromRouteData(outlet: IonRouterOutlet): void {
|
||||
this.outletDataSubscriptions.get(outlet)?.unsubscribe();
|
||||
this.outletDataSubscriptions.delete(outlet);
|
||||
}
|
||||
|
||||
private subscribeToRouteData(outlet: IonRouterOutlet) {
|
||||
const { activatedRoute } = outlet;
|
||||
const dataSubscription = combineLatest([activatedRoute.queryParams, activatedRoute.params, activatedRoute.data])
|
||||
.pipe(
|
||||
switchMap(([queryParams, params, data], index) => {
|
||||
data = { ...queryParams, ...params, ...data };
|
||||
// Get the first result from the data subscription synchronously so it's available to
|
||||
// the component as soon as possible (and doesn't require a second change detection).
|
||||
if (index === 0) {
|
||||
return of(data);
|
||||
}
|
||||
// Promise.resolve is used to avoid synchronously writing the wrong data when
|
||||
// two of the Observables in the `combineLatest` stream emit one after
|
||||
// another.
|
||||
return Promise.resolve(data);
|
||||
})
|
||||
)
|
||||
.subscribe((data) => {
|
||||
// Outlet may have been deactivated or changed names to be associated with a different
|
||||
// route
|
||||
if (
|
||||
!outlet.isActivated ||
|
||||
!outlet.activatedComponentRef ||
|
||||
outlet.activatedRoute !== activatedRoute ||
|
||||
activatedRoute.component === null
|
||||
) {
|
||||
this.unsubscribeFromRouteData(outlet);
|
||||
return;
|
||||
}
|
||||
|
||||
const mirror = reflectComponentType(activatedRoute.component);
|
||||
if (!mirror) {
|
||||
this.unsubscribeFromRouteData(outlet);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const { templateName } of mirror.inputs) {
|
||||
outlet.activatedComponentRef.setInput(templateName, data[templateName]);
|
||||
}
|
||||
});
|
||||
|
||||
this.outletDataSubscriptions.set(outlet, dataSubscription);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { CommonModule, DOCUMENT } from '@angular/common';
|
||||
import { ModuleWithProviders, APP_INITIALIZER, NgModule, NgZone } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { IonicConfig } from '@ionic/core';
|
||||
|
||||
import { appInitialize } from './app-initialize';
|
||||
@ -11,7 +12,7 @@ import {
|
||||
TextValueAccessorDirective,
|
||||
} from './directives/control-value-accessors';
|
||||
import { IonBackButtonDelegateDirective } from './directives/navigation/ion-back-button';
|
||||
import { IonRouterOutlet } from './directives/navigation/ion-router-outlet';
|
||||
import { INPUT_BINDER, IonRouterOutlet, RoutedComponentInputBinder } from './directives/navigation/ion-router-outlet';
|
||||
import { IonTabs } from './directives/navigation/ion-tabs';
|
||||
import { NavDelegate } from './directives/navigation/nav-delegate';
|
||||
import {
|
||||
@ -71,7 +72,23 @@ export class IonicModule {
|
||||
multi: true,
|
||||
deps: [ConfigToken, DOCUMENT, NgZone],
|
||||
},
|
||||
{
|
||||
provide: INPUT_BINDER,
|
||||
useFactory: componentInputBindingFactory,
|
||||
deps: [Router],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function componentInputBindingFactory(router?: Router) {
|
||||
/**
|
||||
* We cast the router to any here, since the componentInputBindingEnabled
|
||||
* property is not available until Angular v16.
|
||||
*/
|
||||
if ((router as any)?.componentInputBindingEnabled) {
|
||||
return new RoutedComponentInputBinder();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -1,30 +1,43 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Provides a way to customize when activated routes get reused.
|
||||
*/
|
||||
export class IonicRouteStrategy implements RouteReuseStrategy {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
/**
|
||||
* Whether the given route should detach for later reuse.
|
||||
*/
|
||||
shouldDetach(_route: ActivatedRouteSnapshot): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
/**
|
||||
* Returns `false`, meaning the route (and its subtree) is never reattached
|
||||
*/
|
||||
shouldAttach(_route: ActivatedRouteSnapshot): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
store(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_route: ActivatedRouteSnapshot,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_detachedTree: DetachedRouteHandle
|
||||
): void {
|
||||
/**
|
||||
* A no-op; the route is never stored since this strategy never detaches routes for later re-use.
|
||||
*/
|
||||
store(_route: ActivatedRouteSnapshot, _detachedTree: DetachedRouteHandle): void {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
/**
|
||||
* Returns `null` because this strategy does not store routes for later re-use.
|
||||
*/
|
||||
retrieve(_route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a route should be reused.
|
||||
* This strategy returns `true` when the future route config and
|
||||
* current route config are identical and all route parameters are identical.
|
||||
*/
|
||||
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
|
||||
if (future.routeConfig !== curr.routeConfig) {
|
||||
return false;
|
||||
|
@ -0,0 +1,8 @@
|
||||
it("binding route data to inputs should work", () => {
|
||||
cy.visit('/version-test/bind-route/test?query=test');
|
||||
|
||||
cy.get('#route-params').contains('test');
|
||||
cy.get('#query-params').contains('test');
|
||||
cy.get('#data').contains('data:bindToComponentInputs');
|
||||
cy.get('#resolve').contains('resolve:bindToComponentInputs');
|
||||
});
|
@ -0,0 +1,10 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, { bindToComponentInputs: true })],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
@ -0,0 +1,41 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bind-route',
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>bindToComponentInputs</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<div>
|
||||
<h3>Bind Route</h3>
|
||||
<p id="route-params">Route params: id: {{id}}</p>
|
||||
<p id="query-params">Query params: query: {{query}}</p>
|
||||
<p id="data">Data: title: {{title}}</p>
|
||||
<p id="resolve">Resolve: name: {{name}}</p>
|
||||
</div>
|
||||
</ion-content>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [IonicModule]
|
||||
})
|
||||
export class BindComponentInputsComponent implements OnInit {
|
||||
|
||||
@Input() id?: string; // path parameter
|
||||
@Input() query?: string; // query parameter
|
||||
@Input() title?: string; // data property
|
||||
@Input() name?: string; // resolve property
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log('BindComponentInputsComponent.ngOnInit', {
|
||||
id: this.id,
|
||||
query: this.query,
|
||||
title: this.title,
|
||||
name: this.name
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -6,7 +6,17 @@ import { RouterModule } from "@angular/router";
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: 'modal-nav-params',
|
||||
loadComponent: () => import('./modal-nav-params/modal-nav-params.component').then(m => m.ModalNavParamsComponent)
|
||||
loadComponent: () => import('./modal-nav-params/modal-nav-params.component').then(m => m.ModalNavParamsComponent),
|
||||
},
|
||||
{
|
||||
path: 'bind-route/:id',
|
||||
data: {
|
||||
title: 'data:bindToComponentInputs'
|
||||
},
|
||||
resolve: {
|
||||
name: () => 'resolve:bindToComponentInputs'
|
||||
},
|
||||
loadComponent: () => import('./bind-component-inputs/bind-component-inputs.component').then(c => c.BindComponentInputsComponent)
|
||||
}
|
||||
])
|
||||
],
|
||||
|
@ -1,85 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { InputsComponent } from './inputs/inputs.component';
|
||||
import { ModalComponent } from './modal/modal.component';
|
||||
import { RouterLinkComponent } from './router-link/router-link.component';
|
||||
import { RouterLinkPageComponent } from './router-link-page/router-link-page.component';
|
||||
import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component';
|
||||
import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component';
|
||||
import { HomePageComponent } from './home-page/home-page.component';
|
||||
import { NestedOutletComponent } from './nested-outlet/nested-outlet.component';
|
||||
import { NestedOutletPageComponent } from './nested-outlet-page/nested-outlet-page.component';
|
||||
import { NestedOutletPage2Component } from './nested-outlet-page2/nested-outlet-page2.component';
|
||||
import { ViewChildComponent } from './view-child/view-child.component';
|
||||
import { ProvidersComponent } from './providers/providers.component';
|
||||
import { FormComponent } from './form/form.component';
|
||||
import { NavigationPage1Component } from './navigation-page1/navigation-page1.component';
|
||||
import { NavigationPage2Component } from './navigation-page2/navigation-page2.component';
|
||||
import { NavigationPage3Component } from './navigation-page3/navigation-page3.component';
|
||||
import { AlertComponent } from './alert/alert.component';
|
||||
import { AccordionComponent } from './accordion/accordion.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: HomePageComponent },
|
||||
{ path: 'version-test', loadChildren: () => import('./version-test').then(m => m.VersionTestModule) },
|
||||
{ path: 'accordions', component: AccordionComponent },
|
||||
{ path: 'alerts', component: AlertComponent },
|
||||
{ path: 'inputs', component: InputsComponent },
|
||||
{ path: 'textarea', loadChildren: () => import('./textarea/textarea.module').then(m => m.TextareaModule) },
|
||||
{ path: 'searchbar', loadChildren: () => import('./searchbar/searchbar.module').then(m => m.SearchbarModule) },
|
||||
{ path: 'form', component: FormComponent },
|
||||
{ path: 'modals', component: ModalComponent },
|
||||
{ path: 'modal-inline', loadChildren: () => import('./modal-inline').then(m => m.ModalInlineModule) },
|
||||
{ path: 'view-child', component: ViewChildComponent },
|
||||
{ path: 'keep-contents-mounted', loadChildren: () => import('./keep-contents-mounted').then(m => m.OverlayAutoMountModule) },
|
||||
{ path: 'overlays-inline', loadChildren: () => import('./overlays-inline').then(m => m.OverlaysInlineModule) },
|
||||
{ path: 'popover-inline', loadChildren: () => import('./popover-inline').then(m => m.PopoverInlineModule) },
|
||||
{ path: 'providers', component: ProvidersComponent },
|
||||
{ path: 'router-link', component: RouterLinkComponent },
|
||||
{ path: 'router-link-page', component: RouterLinkPageComponent },
|
||||
{ path: 'router-link-page2/:id', component: RouterLinkPage2Component },
|
||||
{ path: 'router-link-page3', component: RouterLinkPage3Component },
|
||||
{ path: 'standalone', loadComponent: () => import('./standalone/standalone.component').then(c => c.StandaloneComponent) },
|
||||
{ path: 'tabs', redirectTo: '/tabs/account', pathMatch: 'full' },
|
||||
{
|
||||
path: 'navigation',
|
||||
children: [
|
||||
{ path: 'page1', component: NavigationPage1Component },
|
||||
{ path: 'page2', component: NavigationPage2Component },
|
||||
{ path: 'page3', component: NavigationPage3Component }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'tabs',
|
||||
loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
|
||||
},
|
||||
{
|
||||
path: 'tabs-global',
|
||||
loadChildren: () => import('./tabs-global/tabs-global.module').then(m => m.TabsGlobalModule)
|
||||
},
|
||||
{
|
||||
path: 'tabs-slots',
|
||||
loadComponent: () => import('./tabs-slots.component').then(c => c.TabsSlotsComponent)
|
||||
},
|
||||
{
|
||||
path: 'nested-outlet',
|
||||
component: NestedOutletComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'page',
|
||||
component: NestedOutletPageComponent
|
||||
},
|
||||
{
|
||||
path: 'page2',
|
||||
component: NestedOutletPage2Component
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'form-controls/range',
|
||||
loadChildren: () => import('./form-controls/range/range.module').then(m => m.RangeModule)
|
||||
}
|
||||
];
|
||||
import { routes } from './app.routes';
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
|
82
packages/angular/test/base/src/app/app.routes.ts
Normal file
82
packages/angular/test/base/src/app/app.routes.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { InputsComponent } from './inputs/inputs.component';
|
||||
import { ModalComponent } from './modal/modal.component';
|
||||
import { RouterLinkComponent } from './router-link/router-link.component';
|
||||
import { RouterLinkPageComponent } from './router-link-page/router-link-page.component';
|
||||
import { RouterLinkPage2Component } from './router-link-page2/router-link-page2.component';
|
||||
import { RouterLinkPage3Component } from './router-link-page3/router-link-page3.component';
|
||||
import { HomePageComponent } from './home-page/home-page.component';
|
||||
import { NestedOutletComponent } from './nested-outlet/nested-outlet.component';
|
||||
import { NestedOutletPageComponent } from './nested-outlet-page/nested-outlet-page.component';
|
||||
import { NestedOutletPage2Component } from './nested-outlet-page2/nested-outlet-page2.component';
|
||||
import { ViewChildComponent } from './view-child/view-child.component';
|
||||
import { ProvidersComponent } from './providers/providers.component';
|
||||
import { FormComponent } from './form/form.component';
|
||||
import { NavigationPage1Component } from './navigation-page1/navigation-page1.component';
|
||||
import { NavigationPage2Component } from './navigation-page2/navigation-page2.component';
|
||||
import { NavigationPage3Component } from './navigation-page3/navigation-page3.component';
|
||||
import { AlertComponent } from './alert/alert.component';
|
||||
import { AccordionComponent } from './accordion/accordion.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: HomePageComponent },
|
||||
{ path: 'version-test', loadChildren: () => import('./version-test').then(m => m.VersionTestModule) },
|
||||
{ path: 'accordions', component: AccordionComponent },
|
||||
{ path: 'alerts', component: AlertComponent },
|
||||
{ path: 'inputs', component: InputsComponent },
|
||||
{ path: 'textarea', loadChildren: () => import('./textarea/textarea.module').then(m => m.TextareaModule) },
|
||||
{ path: 'searchbar', loadChildren: () => import('./searchbar/searchbar.module').then(m => m.SearchbarModule) },
|
||||
{ path: 'form', component: FormComponent },
|
||||
{ path: 'modals', component: ModalComponent },
|
||||
{ path: 'modal-inline', loadChildren: () => import('./modal-inline').then(m => m.ModalInlineModule) },
|
||||
{ path: 'view-child', component: ViewChildComponent },
|
||||
{ path: 'keep-contents-mounted', loadChildren: () => import('./keep-contents-mounted').then(m => m.OverlayAutoMountModule) },
|
||||
{ path: 'overlays-inline', loadChildren: () => import('./overlays-inline').then(m => m.OverlaysInlineModule) },
|
||||
{ path: 'popover-inline', loadChildren: () => import('./popover-inline').then(m => m.PopoverInlineModule) },
|
||||
{ path: 'providers', component: ProvidersComponent },
|
||||
{ path: 'router-link', component: RouterLinkComponent },
|
||||
{ path: 'router-link-page', component: RouterLinkPageComponent },
|
||||
{ path: 'router-link-page2/:id', component: RouterLinkPage2Component },
|
||||
{ path: 'router-link-page3', component: RouterLinkPage3Component },
|
||||
{ path: 'standalone', loadComponent: () => import('./standalone/standalone.component').then(c => c.StandaloneComponent) },
|
||||
{ path: 'tabs', redirectTo: '/tabs/account', pathMatch: 'full' },
|
||||
{
|
||||
path: 'navigation',
|
||||
children: [
|
||||
{ path: 'page1', component: NavigationPage1Component },
|
||||
{ path: 'page2', component: NavigationPage2Component },
|
||||
{ path: 'page3', component: NavigationPage3Component }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'tabs',
|
||||
loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
|
||||
},
|
||||
{
|
||||
path: 'tabs-global',
|
||||
loadChildren: () => import('./tabs-global/tabs-global.module').then(m => m.TabsGlobalModule)
|
||||
},
|
||||
{
|
||||
path: 'tabs-slots',
|
||||
loadComponent: () => import('./tabs-slots.component').then(c => c.TabsSlotsComponent)
|
||||
},
|
||||
{
|
||||
path: 'nested-outlet',
|
||||
component: NestedOutletComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'page',
|
||||
component: NestedOutletPageComponent
|
||||
},
|
||||
{
|
||||
path: 'page2',
|
||||
component: NestedOutletPage2Component
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'form-controls/range',
|
||||
loadChildren: () => import('./form-controls/range/range.module').then(m => m.RangeModule)
|
||||
}
|
||||
];
|
||||
|
Reference in New Issue
Block a user