mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
feat(router): Add transient info to RouterLink input (#53784)
This is a follow up to 5c1d441029
which added the `info` property to navigation requests. `RouterLink` now
supports passing that transient navigation info to the navigation
request.
This info object can be anything and doesn't have to be serializable.
One use-case might be for passing the element that was clicked. This
might be useful for something like view transitions. In the "animating
with javascript" example from the blog (https://stackblitz.com/edit/stackblitz-starters-cklnkm)
those links could have done this instead of needing to create a separate
directive that tracks clicks.
PR Close #53784
This commit is contained in:
parent
91f250dab7
commit
a5a9b408e2
3 changed files with 43 additions and 3 deletions
|
|
@ -782,6 +782,7 @@ class RouterLink implements OnChanges, OnDestroy {
|
|||
constructor(router: Router, route: ActivatedRoute, tabIndexAttribute: string | null | undefined, renderer: Renderer2, el: ElementRef, locationStrategy?: LocationStrategy | undefined);
|
||||
fragment?: string;
|
||||
href: string | null;
|
||||
info?: unknown;
|
||||
// (undocumented)
|
||||
static ngAcceptInputType_preserveFragment: unknown;
|
||||
// (undocumented)
|
||||
|
|
@ -808,7 +809,7 @@ class RouterLink implements OnChanges, OnDestroy {
|
|||
// (undocumented)
|
||||
get urlTree(): UrlTree | null;
|
||||
// (undocumented)
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<RouterLink, "[routerLink]", never, { "target": { "alias": "target"; "required": false; }; "queryParams": { "alias": "queryParams"; "required": false; }; "fragment": { "alias": "fragment"; "required": false; }; "queryParamsHandling": { "alias": "queryParamsHandling"; "required": false; }; "state": { "alias": "state"; "required": false; }; "relativeTo": { "alias": "relativeTo"; "required": false; }; "preserveFragment": { "alias": "preserveFragment"; "required": false; }; "skipLocationChange": { "alias": "skipLocationChange"; "required": false; }; "replaceUrl": { "alias": "replaceUrl"; "required": false; }; "routerLink": { "alias": "routerLink"; "required": false; }; }, {}, never, never, true, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<RouterLink, "[routerLink]", never, { "target": { "alias": "target"; "required": false; }; "queryParams": { "alias": "queryParams"; "required": false; }; "fragment": { "alias": "fragment"; "required": false; }; "queryParamsHandling": { "alias": "queryParamsHandling"; "required": false; }; "state": { "alias": "state"; "required": false; }; "info": { "alias": "info"; "required": false; }; "relativeTo": { "alias": "relativeTo"; "required": false; }; "preserveFragment": { "alias": "preserveFragment"; "required": false; }; "skipLocationChange": { "alias": "skipLocationChange"; "required": false; }; "replaceUrl": { "alias": "replaceUrl"; "required": false; }; "routerLink": { "alias": "routerLink"; "required": false; }; }, {}, never, never, true, never>;
|
||||
// (undocumented)
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<RouterLink, [null, null, { attribute: "tabindex"; }, null, null, null]>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,6 +160,13 @@ export class RouterLink implements OnChanges, OnDestroy {
|
|||
* @see {@link Router#navigateByUrl}
|
||||
*/
|
||||
@Input() state?: {[k: string]: any};
|
||||
/**
|
||||
* Passed to {@link Router#navigateByUrl} as part of the
|
||||
* `NavigationBehaviorOptions`.
|
||||
* @see {@link NavigationBehaviorOptions#info}
|
||||
* @see {@link Router#navigateByUrl}
|
||||
*/
|
||||
@Input() info?: unknown;
|
||||
/**
|
||||
* Passed to {@link Router#createUrlTree} as part of the
|
||||
* `UrlCreationOptions`.
|
||||
|
|
@ -287,6 +294,7 @@ export class RouterLink implements OnChanges, OnDestroy {
|
|||
skipLocationChange: this.skipLocationChange,
|
||||
replaceUrl: this.replaceUrl,
|
||||
state: this.state,
|
||||
info: this.info,
|
||||
};
|
||||
this.router.navigateByUrl(this.urlTree, extras);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
*/
|
||||
|
||||
import {CommonModule, HashLocationStrategy, Location, LocationStrategy, PlatformLocation, PopStateEvent} from '@angular/common';
|
||||
import {ChangeDetectionStrategy, Component, EnvironmentInjector, inject as coreInject, Inject, Injectable, InjectionToken, NgModule, NgModuleRef, NgZone, OnDestroy, QueryList, Type, ViewChild, ViewChildren, ɵConsole as Console, ɵNoopNgZone as NoopNgZone} from '@angular/core';
|
||||
import {ApplicationRef, ChangeDetectionStrategy, Component, EnvironmentInjector, inject as coreInject, Inject, Injectable, InjectionToken, NgModule, NgModuleRef, NgZone, OnDestroy, QueryList, Type, ViewChild, ViewChildren, ɵConsole as Console, ɵNoopNgZone as NoopNgZone} from '@angular/core';
|
||||
import {ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, ChildActivationEnd, ChildActivationStart, DefaultUrlSerializer, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, Navigation, NavigationCancel, NavigationCancellationCode, NavigationEnd, NavigationError, NavigationSkipped, NavigationStart, ParamMap, Params, PreloadAllModules, PreloadingStrategy, PRIMARY_OUTLET, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouteReuseStrategy, RouterEvent, RouterLink, RouterLinkActive, RouterModule, RouterOutlet, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from '@angular/router';
|
||||
import {RouterTestingHarness} from '@angular/router/testing';
|
||||
import {concat, EMPTY, Observable, Observer, of, Subscription} from 'rxjs';
|
||||
import {concat, EMPTY, firstValueFrom, Observable, Observer, of, Subscription} from 'rxjs';
|
||||
import {delay, filter, first, last, map, mapTo, takeWhile, tap} from 'rxjs/operators';
|
||||
|
||||
import {CanActivateChildFn, CanActivateFn, CanMatchFn, Data, ResolveFn} from '../src/models';
|
||||
|
|
@ -158,6 +158,37 @@ describe('Integration', () => {
|
|||
expect(observedInfo).toEqual('navigation info');
|
||||
});
|
||||
|
||||
it('should set transient navigation info for routerlink', async () => {
|
||||
let observedInfo: unknown;
|
||||
const router = TestBed.inject(Router);
|
||||
router.resetConfig([
|
||||
{
|
||||
path: 'simple',
|
||||
component: SimpleCmp,
|
||||
canActivate: [() => {
|
||||
observedInfo = coreInject(Router).getCurrentNavigation()?.extras?.info;
|
||||
return true;
|
||||
}]
|
||||
},
|
||||
]);
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [RouterLink],
|
||||
template: `<a #simpleLink [routerLink]="'/simple'" [info]="simpleLink"></a>`
|
||||
})
|
||||
class App {
|
||||
}
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.autoDetectChanges();
|
||||
const anchor = fixture.nativeElement.querySelector('a');
|
||||
anchor.click();
|
||||
await fixture.whenStable();
|
||||
|
||||
// An example use-case might be to pass the clicked link along with the navigation information
|
||||
expect(observedInfo).toBeInstanceOf(HTMLAnchorElement);
|
||||
});
|
||||
|
||||
it('should make transient navigation info available in redirect', async () => {
|
||||
let observedInfo: unknown;
|
||||
const router = TestBed.inject(Router);
|
||||
|
|
|
|||
Loading…
Reference in a new issue