/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer, SimpleChanges} from '@angular/core'; import {Subscription} from 'rxjs/Subscription'; import {NavigationEnd} from '../events'; import {Router} from '../router'; import {RouterLink, RouterLinkWithHref} from './router_link'; /** * @whatItDoes Lets you add a CSS class to an element when the link's route becomes active. * * @howToUse * * ``` * Bob * ``` * * @description * * The RouterLinkActive directive lets you add a CSS class to an element when the link's route * becomes active. * * Consider the following example: * * ``` * Bob * ``` * * When the url is either '/user' or '/user/bob', the active-link class will * be added to the `a` tag. If the url changes, the class will be removed. * * You can set more than one class, as follows: * * ``` * Bob * Bob * ``` * * You can configure RouterLinkActive by passing `exact: true`. This will add the classes * only when the url matches the link exactly. * * ``` * Bob * ``` * * You can assign the RouterLinkActive instance to a template variable and directly check * the `isActive` status. * ``` * * Bob {{ rla.isActive ? '(already open)' : ''}} * * ``` * * Finally, you can apply the RouterLinkActive directive to an ancestor of a RouterLink. * * ``` *
* Jim * Bob *
* ``` * * This will set the active-link class on the div tag if the url is either '/user/jim' or * '/user/bob'. * * @ngModule RouterModule * * @stable */ @Directive({ selector: '[routerLinkActive]', exportAs: 'routerLinkActive', }) export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit { @ContentChildren(RouterLink, {descendants: true}) links: QueryList; @ContentChildren(RouterLinkWithHref, {descendants: true}) linksWithHrefs: QueryList; private classes: string[] = []; private subscription: Subscription; private active: boolean = false; @Input() routerLinkActiveOptions: {exact: boolean} = {exact: false}; constructor( private router: Router, private element: ElementRef, private renderer: Renderer, private cdr: ChangeDetectorRef) { this.subscription = router.events.subscribe(s => { if (s instanceof NavigationEnd) { this.update(); } }); } get isActive(): boolean { return this.active; } ngAfterContentInit(): void { this.links.changes.subscribe(_ => this.update()); this.linksWithHrefs.changes.subscribe(_ => this.update()); this.update(); } @Input() set routerLinkActive(data: string[]|string) { const classes = Array.isArray(data) ? data : data.split(' '); this.classes = classes.filter(c => !!c); } ngOnChanges(changes: SimpleChanges): void { this.update(); } ngOnDestroy(): void { this.subscription.unsubscribe(); } private update(): void { if (!this.links || !this.linksWithHrefs || !this.router.navigated) return; const hasActiveLinks = this.hasActiveLinks(); // react only when status has changed to prevent unnecessary dom updates if (this.active !== hasActiveLinks) { this.classes.forEach( c => this.renderer.setElementClass(this.element.nativeElement, c, hasActiveLinks)); Promise.resolve(hasActiveLinks).then(active => this.active = active); } } private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean { return (link: RouterLink | RouterLinkWithHref) => router.isActive(link.urlTree, this.routerLinkActiveOptions.exact); } private hasActiveLinks(): boolean { return this.links.some(this.isLinkActive(this.router)) || this.linksWithHrefs.some(this.isLinkActive(this.router)); } }