fix(router): make router links work on non-a tags
This commit is contained in:
parent
810c722413
commit
8c45aebc18
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RouterLink} from './src/directives/router_link';
|
import {RouterLink, RouterLinkWithHref} from './src/directives/router_link';
|
||||||
import {RouterLinkActive} from './src/directives/router_link_active';
|
import {RouterLinkActive} from './src/directives/router_link_active';
|
||||||
import {RouterOutlet} from './src/directives/router_outlet';
|
import {RouterOutlet} from './src/directives/router_outlet';
|
||||||
|
|
||||||
|
@ -24,4 +24,4 @@ export {DefaultUrlSerializer, UrlPathWithParams, UrlSerializer, UrlTree} from '.
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink, RouterLinkActive];
|
export const ROUTER_DIRECTIVES = [RouterOutlet, RouterLink, RouterLinkWithHref, RouterLinkActive];
|
||||||
|
|
|
@ -54,8 +54,54 @@ import {UrlTree} from '../url_tree';
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
@Directive({selector: '[routerLink]'})
|
@Directive({selector: ':not(a)[routerLink]'})
|
||||||
export class RouterLink implements OnChanges {
|
export class RouterLink {
|
||||||
|
@Input() target: string;
|
||||||
|
private commands: any[] = [];
|
||||||
|
@Input() queryParams: {[k: string]: any};
|
||||||
|
@Input() fragment: string;
|
||||||
|
|
||||||
|
urlTree: UrlTree;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private router: Router, private route: ActivatedRoute,
|
||||||
|
private locationStrategy: LocationStrategy) {}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set routerLink(data: any[]|string) {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
this.commands = <any>data;
|
||||||
|
} else {
|
||||||
|
this.commands = [data];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('click', ['$event.button', '$event.ctrlKey', '$event.metaKey'])
|
||||||
|
onClick(button: number, ctrlKey: boolean, metaKey: boolean): boolean {
|
||||||
|
if (button !== 0 || ctrlKey || metaKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.target === 'string' && this.target != '_self') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.router.navigate(
|
||||||
|
this.commands,
|
||||||
|
{relativeTo: this.route, queryParams: this.queryParams, fragment: this.fragment});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link RouterLink} for more information.
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
@Directive({selector: 'a[routerLink]'})
|
||||||
|
export class RouterLinkWithHref implements OnChanges {
|
||||||
@Input() target: string;
|
@Input() target: string;
|
||||||
private commands: any[] = [];
|
private commands: any[] = [];
|
||||||
@Input() queryParams: {[k: string]: any};
|
@Input() queryParams: {[k: string]: any};
|
||||||
|
|
|
@ -10,9 +10,10 @@ import {AfterContentInit, ContentChildren, Directive, ElementRef, Input, OnChang
|
||||||
import {Subscription} from 'rxjs/Subscription';
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
|
|
||||||
import {NavigationEnd, Router} from '../router';
|
import {NavigationEnd, Router} from '../router';
|
||||||
import {containsTree} from '../url_tree';
|
import {UrlTree, containsTree} from '../url_tree';
|
||||||
|
|
||||||
|
import {RouterLink, RouterLinkWithHref} from './router_link';
|
||||||
|
|
||||||
import {RouterLink} from './router_link';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The RouterLinkActive directive lets you add a CSS class to an element when the link's route
|
* The RouterLinkActive directive lets you add a CSS class to an element when the link's route
|
||||||
|
@ -59,6 +60,8 @@ import {RouterLink} from './router_link';
|
||||||
@Directive({selector: '[routerLinkActive]'})
|
@Directive({selector: '[routerLinkActive]'})
|
||||||
export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
|
export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
|
||||||
@ContentChildren(RouterLink) private links: QueryList<RouterLink>;
|
@ContentChildren(RouterLink) private links: QueryList<RouterLink>;
|
||||||
|
@ContentChildren(RouterLinkWithHref) private linksWithHrefs: QueryList<RouterLinkWithHref>;
|
||||||
|
|
||||||
private classes: string[] = [];
|
private classes: string[] = [];
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
|
@ -77,6 +80,7 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||||
|
|
||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
this.links.changes.subscribe(s => this.update());
|
this.links.changes.subscribe(s => this.update());
|
||||||
|
this.linksWithHrefs.changes.subscribe(s => this.update());
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,15 +97,20 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
||||||
ngOnDestroy(): any { this.subscription.unsubscribe(); }
|
ngOnDestroy(): any { this.subscription.unsubscribe(); }
|
||||||
|
|
||||||
private update(): void {
|
private update(): void {
|
||||||
if (!this.links || this.links.length === 0) return;
|
if (!this.links || !this.linksWithHrefs) return;
|
||||||
|
|
||||||
const currentUrlTree = this.router.parseUrl(this.router.url);
|
const currentUrlTree = this.router.parseUrl(this.router.url);
|
||||||
const isActive = this.links.reduce(
|
const isActiveLinks = this.reduceList(currentUrlTree, this.links);
|
||||||
(res, link) =>
|
const isActiveLinksWithHrefs = this.reduceList(currentUrlTree, this.linksWithHrefs);
|
||||||
|
this.classes.forEach(
|
||||||
|
c => this.renderer.setElementClass(
|
||||||
|
this.element.nativeElement, c, isActiveLinks || isActiveLinksWithHrefs));
|
||||||
|
}
|
||||||
|
|
||||||
|
private reduceList(currentUrlTree: UrlTree, q: QueryList<any>): boolean {
|
||||||
|
return q.reduce(
|
||||||
|
(res: boolean, link: any) =>
|
||||||
res || containsTree(currentUrlTree, link.urlTree, this.routerLinkActiveOptions.exact),
|
res || containsTree(currentUrlTree, link.urlTree, this.routerLinkActiveOptions.exact),
|
||||||
false);
|
false);
|
||||||
|
|
||||||
this.classes.forEach(
|
|
||||||
c => this.renderer.setElementClass(this.element.nativeElement, c, isActive));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -476,7 +476,6 @@ describe('Integration', () => {
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('router links', () => {
|
describe('router links', () => {
|
||||||
it('should support string router links',
|
it('should support string router links',
|
||||||
fakeAsync(
|
fakeAsync(
|
||||||
|
@ -504,6 +503,31 @@ describe('Integration', () => {
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('team 33 { simple, right: }');
|
expect(fixture.debugElement.nativeElement).toHaveText('team 33 { simple, right: }');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
it('should support using links on non-a tags',
|
||||||
|
fakeAsync(
|
||||||
|
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
|
||||||
|
const fixture = createRoot(tcb, router, RootCmp);
|
||||||
|
|
||||||
|
router.resetConfig([{
|
||||||
|
path: 'team/:id',
|
||||||
|
component: TeamCmp,
|
||||||
|
children: [
|
||||||
|
{path: 'link', component: StringLinkButtonCmp},
|
||||||
|
{path: 'simple', component: SimpleCmp}
|
||||||
|
]
|
||||||
|
}]);
|
||||||
|
|
||||||
|
router.navigateByUrl('/team/22/link');
|
||||||
|
advance(fixture);
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('team 22 { link, right: }');
|
||||||
|
|
||||||
|
const native = fixture.debugElement.nativeElement.querySelector('button');
|
||||||
|
native.click();
|
||||||
|
advance(fixture);
|
||||||
|
|
||||||
|
expect(fixture.debugElement.nativeElement).toHaveText('team 33 { simple, right: }');
|
||||||
|
})));
|
||||||
|
|
||||||
it('should support absolute router links',
|
it('should support absolute router links',
|
||||||
fakeAsync(
|
fakeAsync(
|
||||||
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
|
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
|
||||||
|
@ -1023,6 +1047,15 @@ function expectEvents(events: Event[], pairs: any[]) {
|
||||||
class StringLinkCmp {
|
class StringLinkCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'link-cmp',
|
||||||
|
template: `<button routerLink
|
||||||
|
="/team/33/simple">link</button>`,
|
||||||
|
directives: ROUTER_DIRECTIVES
|
||||||
|
})
|
||||||
|
class StringLinkButtonCmp {
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'link-cmp',
|
selector: 'link-cmp',
|
||||||
template: `<router-outlet></router-outlet><a [routerLink]="['/team/33/simple']">link</a>`,
|
template: `<router-outlet></router-outlet><a [routerLink]="['/team/33/simple']">link</a>`,
|
||||||
|
@ -1159,7 +1192,7 @@ class RelativeLinkInIfCmp {
|
||||||
precompile: [
|
precompile: [
|
||||||
BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp,
|
BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp,
|
||||||
RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp,
|
RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp,
|
||||||
QueryParamsAndFragmentCmp
|
QueryParamsAndFragmentCmp, StringLinkButtonCmp
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
class RootCmp {
|
class RootCmp {
|
||||||
|
|
|
@ -123,7 +123,7 @@ export declare class Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare const ROUTER_DIRECTIVES: (typeof RouterOutlet | typeof RouterLink | typeof RouterLinkActive)[];
|
export declare const ROUTER_DIRECTIVES: (typeof RouterOutlet | typeof RouterLink | typeof RouterLinkWithHref | typeof RouterLinkActive)[];
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare type RouterConfig = Route[];
|
export declare type RouterConfig = Route[];
|
||||||
|
|
Loading…
Reference in New Issue