diff --git a/docs/overview/links.md b/docs/overview/links.md index c29bd7e..8b6f440 100644 --- a/docs/overview/links.md +++ b/docs/overview/links.md @@ -57,3 +57,15 @@ In order to keep a parent link active when the child path is active, use the `ac Page 1 ``` + +## Default Link Active Options + +To override the default `activeOptions` for the `linkActive` directive, provide the `LINK_ACTIVE_OPTIONS` token in the bootstrap array of the application. + +```ts +import {LINK_ACTIVE_OPTIONS} from '@ngrx/router'; + +bootstrap(App, [ + provide(LINK_ACTIVE_OPTIONS, { useValue: { exact: false } }) +]) +``` diff --git a/lib/index.ts b/lib/index.ts index e8db6cf..2b8d479 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -59,6 +59,6 @@ export { PRE_RENDER_HOOKS, POST_RENDER_HOOKS, RenderInstruction } from './compon export { Routes, Route, IndexRoute } from './route'; export { TRAVERSAL_HOOKS, TraversalCandidate, Match } from './route-traverser'; export { LinkTo } from './link-to'; -export { LinkActive, LinkActiveOptions } from './link-active'; +export { LinkActive, LinkActiveOptions, LINK_ACTIVE_OPTIONS } from './link-active'; export { RouteView } from './route-view'; export { Hook } from './hooks'; diff --git a/lib/link-active.ts b/lib/link-active.ts index 6c7bcf2..5c3b934 100644 --- a/lib/link-active.ts +++ b/lib/link-active.ts @@ -6,7 +6,10 @@ import { OnDestroy, Query, QueryList, - Renderer + Renderer, + Optional, + OpaqueToken, + Inject } from '@angular/core'; import { LinkTo } from './link-to'; import { Router } from './router'; @@ -15,6 +18,10 @@ export interface LinkActiveOptions { exact: boolean; } +export const LINK_ACTIVE_OPTIONS: LinkActiveOptions = { + exact: true +}; + /** * The LinkActive directive toggles classes on elements that contain an active linkTo directive * @@ -28,17 +35,27 @@ export interface LinkActiveOptions { @Directive({ selector: '[linkActive]' }) export class LinkActive implements AfterViewInit, OnDestroy { @Input('linkActive') activeClass: string = 'active'; - @Input() activeOptions: LinkActiveOptions = { exact: true }; + @Input() activeOptions: LinkActiveOptions; private _sub: any; + private _activeOptions: LinkActiveOptions = { exact: true }; constructor( @Query(LinkTo) public links: QueryList, public element: ElementRef, public router$: Router, - public renderer: Renderer + public renderer: Renderer, + @Optional() + @Inject(LINK_ACTIVE_OPTIONS) + private defaultActiveOptions: LinkActiveOptions ) {} ngAfterViewInit() { + if (this.defaultActiveOptions && !this.activeOptions) { + this._activeOptions = this.defaultActiveOptions; + } else if (this.activeOptions) { + this._activeOptions = this.activeOptions; + } + this._sub = this.router$ .map(({path}) => this.router$.prepareExternalUrl(path || '/')) .subscribe(path => { @@ -50,7 +67,7 @@ export interface LinkActiveOptions { let active = this.links.reduce((active, current) => { let [href, query] = current.linkHref.split('?'); - if (this.activeOptions.exact) { + if (this._activeOptions.exact) { return active ? active : href === path; } else { return active ? active : path.startsWith(href); diff --git a/spec/link-active.spec.ts b/spec/link-active.spec.ts index d92d091..3477de7 100644 --- a/spec/link-active.spec.ts +++ b/spec/link-active.spec.ts @@ -16,7 +16,7 @@ import { Observable } from 'rxjs/Observable'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { LinkTo } from '../lib/link-to'; -import { LinkActive } from '../lib/link-active'; +import { LinkActive, LINK_ACTIVE_OPTIONS } from '../lib/link-active'; import { ROUTER_PROVIDERS, Router } from '../lib/router'; @Component({ @@ -185,4 +185,52 @@ describe('Link Active', () => { }); }))); }); + + describe('With Default Link Active Options', () => { + beforeEachProviders(() => [ + ROUTER_PROVIDERS, + provide(LocationStrategy, { useClass: MockLocationStrategy }), + provide(LINK_ACTIVE_OPTIONS, { useValue: { exact: false } }) + ]); + + it('should allow override of default LinkActiveOptions', async(inject([TestComponentBuilder, Router], (tcb, router$) => { + router$.next({ + path: '/pages/page2' + }); + + return compile(tcb, ` + Pages
+ Page 2
+ `) + .then((fixture) => { + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + let pagesLink: Element = compiled.querySelector('#pages'); + let page2Link: Element = compiled.querySelector('#page2'); + + expect(pagesLink.getAttribute('class')).toEqual('active'); + expect(page2Link.getAttribute('class')).toEqual('active'); + }); + }))); + + it('should allow local override of default LinkActiveOptions', async(inject([TestComponentBuilder, Router], (tcb, router$) => { + router$.next({ + path: '/pages/page2' + }); + + return compile(tcb, ` + Pages
+ Page 2
+ `) + .then((fixture) => { + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + let pagesLink: Element = compiled.querySelector('#pages'); + let page2Link: Element = compiled.querySelector('#page2'); + + expect(pagesLink.getAttribute('class')).not.toEqual('active'); + expect(page2Link.getAttribute('class')).toEqual('active'); + }); + }))); + }); });