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
|
||||
*/
|
||||
|
||||
import {RouterLink} from './src/directives/router_link';
|
||||
import {RouterLink, RouterLinkWithHref} from './src/directives/router_link';
|
||||
import {RouterLinkActive} from './src/directives/router_link_active';
|
||||
import {RouterOutlet} from './src/directives/router_outlet';
|
||||
|
||||
|
@ -24,4 +24,4 @@ export {DefaultUrlSerializer, UrlPathWithParams, UrlSerializer, UrlTree} from '.
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
@Directive({selector: '[routerLink]'})
|
||||
export class RouterLink implements OnChanges {
|
||||
@Directive({selector: ':not(a)[routerLink]'})
|
||||
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;
|
||||
private commands: any[] = [];
|
||||
@Input() queryParams: {[k: string]: any};
|
||||
|
|
|
@ -10,9 +10,10 @@ import {AfterContentInit, ContentChildren, Directive, ElementRef, Input, OnChang
|
|||
import {Subscription} from 'rxjs/Subscription';
|
||||
|
||||
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
|
||||
|
@ -59,6 +60,8 @@ import {RouterLink} from './router_link';
|
|||
@Directive({selector: '[routerLinkActive]'})
|
||||
export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
|
||||
@ContentChildren(RouterLink) private links: QueryList<RouterLink>;
|
||||
@ContentChildren(RouterLinkWithHref) private linksWithHrefs: QueryList<RouterLinkWithHref>;
|
||||
|
||||
private classes: string[] = [];
|
||||
private subscription: Subscription;
|
||||
|
||||
|
@ -77,6 +80,7 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
|||
|
||||
ngAfterContentInit(): void {
|
||||
this.links.changes.subscribe(s => this.update());
|
||||
this.linksWithHrefs.changes.subscribe(s => this.update());
|
||||
this.update();
|
||||
}
|
||||
|
||||
|
@ -93,15 +97,20 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
|
|||
ngOnDestroy(): any { this.subscription.unsubscribe(); }
|
||||
|
||||
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 isActive = this.links.reduce(
|
||||
(res, link) =>
|
||||
const isActiveLinks = this.reduceList(currentUrlTree, this.links);
|
||||
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),
|
||||
false);
|
||||
|
||||
this.classes.forEach(
|
||||
c => this.renderer.setElementClass(this.element.nativeElement, c, isActive));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -476,7 +476,6 @@ describe('Integration', () => {
|
|||
})));
|
||||
});
|
||||
|
||||
|
||||
describe('router links', () => {
|
||||
it('should support string router links',
|
||||
fakeAsync(
|
||||
|
@ -504,6 +503,31 @@ describe('Integration', () => {
|
|||
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',
|
||||
fakeAsync(
|
||||
inject([Router, TestComponentBuilder], (router: Router, tcb: TestComponentBuilder) => {
|
||||
|
@ -1023,6 +1047,15 @@ function expectEvents(events: Event[], pairs: any[]) {
|
|||
class StringLinkCmp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'link-cmp',
|
||||
template: `<button routerLink
|
||||
="/team/33/simple">link</button>`,
|
||||
directives: ROUTER_DIRECTIVES
|
||||
})
|
||||
class StringLinkButtonCmp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'link-cmp',
|
||||
template: `<router-outlet></router-outlet><a [routerLink]="['/team/33/simple']">link</a>`,
|
||||
|
@ -1159,7 +1192,7 @@ class RelativeLinkInIfCmp {
|
|||
precompile: [
|
||||
BlankCmp, SimpleCmp, TeamCmp, UserCmp, StringLinkCmp, DummyLinkCmp, AbsoluteLinkCmp,
|
||||
RelativeLinkCmp, DummyLinkWithParentCmp, LinkWithQueryParamsAndFragment, CollectParamsCmp,
|
||||
QueryParamsAndFragmentCmp
|
||||
QueryParamsAndFragmentCmp, StringLinkButtonCmp
|
||||
]
|
||||
})
|
||||
class RootCmp {
|
||||
|
|
|
@ -123,7 +123,7 @@ export declare class Router {
|
|||
}
|
||||
|
||||
/** @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 */
|
||||
export declare type RouterConfig = Route[];
|
||||
|
|
Loading…
Reference in New Issue