mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 18:17:31 +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,
|
Output,
|
||||||
SkipSelf,
|
SkipSelf,
|
||||||
EnvironmentInjector,
|
EnvironmentInjector,
|
||||||
|
Input,
|
||||||
|
InjectionToken,
|
||||||
|
Injectable,
|
||||||
|
reflectComponentType,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { OutletContext, Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET, Data } from '@angular/router';
|
import { OutletContext, Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET, Data } from '@angular/router';
|
||||||
import { componentOnReady } from '@ionic/core';
|
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 { distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { AnimationBuilder } from '../../ionic-core';
|
import { AnimationBuilder } from '../../ionic-core';
|
||||||
@ -39,22 +43,28 @@ import { RouteView, getUrl } from './stack-utils';
|
|||||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||||
export class IonRouterOutlet implements OnDestroy, OnInit {
|
export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||||
nativeEl: HTMLIonRouterOutletElement;
|
nativeEl: HTMLIonRouterOutletElement;
|
||||||
|
|
||||||
private activated: ComponentRef<any> | null = null;
|
|
||||||
activatedView: RouteView | null = null;
|
activatedView: RouteView | null = null;
|
||||||
|
tabsPrefix: string | undefined;
|
||||||
|
|
||||||
private _activatedRoute: ActivatedRoute | null = null;
|
|
||||||
private _swipeGesture?: boolean;
|
private _swipeGesture?: boolean;
|
||||||
private name: string;
|
|
||||||
private stackCtrl: StackController;
|
private stackCtrl: StackController;
|
||||||
|
|
||||||
// Maintain map of activated route proxies for each component instance
|
// Maintain map of activated route proxies for each component instance
|
||||||
private proxyMap = new WeakMap<any, ActivatedRoute>();
|
private proxyMap = new WeakMap<any, ActivatedRoute>();
|
||||||
|
|
||||||
// Keep the latest activated route in a subject for the proxy routes to switch map to
|
// 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);
|
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>();
|
@Output() stackEvents = new EventEmitter<any>();
|
||||||
// eslint-disable-next-line @angular-eslint/no-output-rename
|
// eslint-disable-next-line @angular-eslint/no-output-rename
|
||||||
@ -65,6 +75,9 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
|||||||
private parentContexts = inject(ChildrenOutletContexts);
|
private parentContexts = inject(ChildrenOutletContexts);
|
||||||
private location = inject(ViewContainerRef);
|
private location = inject(ViewContainerRef);
|
||||||
private environmentInjector = inject(EnvironmentInjector);
|
private environmentInjector = inject(EnvironmentInjector);
|
||||||
|
private inputBinder = inject(INPUT_BINDER, { optional: true });
|
||||||
|
/** @nodoc */
|
||||||
|
readonly supportsBindingToComponentInputs = true;
|
||||||
|
|
||||||
// Ionic providers
|
// Ionic providers
|
||||||
private config = inject(Config);
|
private config = inject(Config);
|
||||||
@ -109,6 +122,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.stackCtrl.destroy();
|
this.stackCtrl.destroy();
|
||||||
|
this.inputBinder?.unsubscribeFromRouteData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
getContext(): OutletContext | null {
|
getContext(): OutletContext | null {
|
||||||
@ -275,6 +289,8 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
|||||||
this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
|
this.currentActivatedRoute$.next({ component: cmpRef.instance, activatedRoute });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.inputBinder?.bindActivatedRouteToOutletComponent(this);
|
||||||
|
|
||||||
this.activatedView = enteringView;
|
this.activatedView = enteringView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -415,3 +431,79 @@ class OutletInjector implements Injector {
|
|||||||
return this.parent.get(token, notFoundValue);
|
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 { CommonModule, DOCUMENT } from '@angular/common';
|
||||||
import { ModuleWithProviders, APP_INITIALIZER, NgModule, NgZone } from '@angular/core';
|
import { ModuleWithProviders, APP_INITIALIZER, NgModule, NgZone } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
import { IonicConfig } from '@ionic/core';
|
import { IonicConfig } from '@ionic/core';
|
||||||
|
|
||||||
import { appInitialize } from './app-initialize';
|
import { appInitialize } from './app-initialize';
|
||||||
@ -11,7 +12,7 @@ import {
|
|||||||
TextValueAccessorDirective,
|
TextValueAccessorDirective,
|
||||||
} from './directives/control-value-accessors';
|
} from './directives/control-value-accessors';
|
||||||
import { IonBackButtonDelegateDirective } from './directives/navigation/ion-back-button';
|
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 { IonTabs } from './directives/navigation/ion-tabs';
|
||||||
import { NavDelegate } from './directives/navigation/nav-delegate';
|
import { NavDelegate } from './directives/navigation/nav-delegate';
|
||||||
import {
|
import {
|
||||||
@ -71,7 +72,23 @@ export class IonicModule {
|
|||||||
multi: true,
|
multi: true,
|
||||||
deps: [ConfigToken, DOCUMENT, NgZone],
|
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';
|
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a way to customize when activated routes get reused.
|
||||||
|
*/
|
||||||
export class IonicRouteStrategy implements RouteReuseStrategy {
|
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 {
|
shouldDetach(_route: ActivatedRouteSnapshot): boolean {
|
||||||
return false;
|
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 {
|
shouldAttach(_route: ActivatedRouteSnapshot): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
store(
|
/**
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
* A no-op; the route is never stored since this strategy never detaches routes for later re-use.
|
||||||
_route: ActivatedRouteSnapshot,
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
store(_route: ActivatedRouteSnapshot, _detachedTree: DetachedRouteHandle): void {
|
||||||
_detachedTree: DetachedRouteHandle
|
|
||||||
): void {
|
|
||||||
return;
|
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 {
|
retrieve(_route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
|
||||||
return 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 {
|
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
|
||||||
if (future.routeConfig !== curr.routeConfig) {
|
if (future.routeConfig !== curr.routeConfig) {
|
||||||
return false;
|
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([
|
RouterModule.forChild([
|
||||||
{
|
{
|
||||||
path: 'modal-nav-params',
|
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 { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { 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';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
import { routes } from './app.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)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
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