diff --git a/lib/link-active.ts b/lib/link-active.ts index 5c3b934..90ff27b 100644 --- a/lib/link-active.ts +++ b/lib/link-active.ts @@ -4,6 +4,7 @@ import { ElementRef, Input, OnDestroy, + OnInit, Query, QueryList, Renderer, @@ -13,6 +14,8 @@ import { } from '@angular/core'; import { LinkTo } from './link-to'; import { Router } from './router'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/mergeAll'; export interface LinkActiveOptions { exact: boolean; @@ -33,11 +36,12 @@ export const LINK_ACTIVE_OPTIONS: LinkActiveOptions = { * */ @Directive({ selector: '[linkActive]' }) - export class LinkActive implements AfterViewInit, OnDestroy { + export class LinkActive implements AfterViewInit, OnInit, OnDestroy { @Input('linkActive') activeClass: string = 'active'; @Input() activeOptions: LinkActiveOptions; - private _sub: any; private _activeOptions: LinkActiveOptions = { exact: true }; + private _routerSub: any; + private _linksSub: any; constructor( @Query(LinkTo) public links: QueryList, @@ -49,6 +53,12 @@ export const LINK_ACTIVE_OPTIONS: LinkActiveOptions = { private defaultActiveOptions: LinkActiveOptions ) {} + ngOnInit () { + this.links.changes.subscribe(_ => { + this.subscribeLinks(); + }); + } + ngAfterViewInit() { if (this.defaultActiveOptions && !this.activeOptions) { this._activeOptions = this.defaultActiveOptions; @@ -56,7 +66,7 @@ export const LINK_ACTIVE_OPTIONS: LinkActiveOptions = { this._activeOptions = this.activeOptions; } - this._sub = this.router$ + this._routerSub = this.router$ .map(({path}) => this.router$.prepareExternalUrl(path || '/')) .subscribe(path => { this.checkActive(path); @@ -80,9 +90,28 @@ export const LINK_ACTIVE_OPTIONS: LinkActiveOptions = { }); } + subscribeLinks() { + if (this._linksSub) { + this._linksSub.unsubscribe(); + } + + let observables = this.links.map(link => { + return link.hrefUpdated; + }); + + this._linksSub = Observable.from(observables) + .mergeAll() + .subscribe(_ => { + this.checkActive(this.router$.prepareExternalUrl(this.router$.path() || '/')); + }); + } + ngOnDestroy() { - if (this._sub) { - this._sub.unsubscribe(); + if (this._routerSub) { + this._routerSub.unsubscribe(); + } + if (this._linksSub) { + this._linksSub.unsubscribe(); } } } diff --git a/lib/link-to.ts b/lib/link-to.ts index d8f596f..fc539e5 100644 --- a/lib/link-to.ts +++ b/lib/link-to.ts @@ -1,4 +1,4 @@ -import { Directive, HostBinding, HostListener, Input } from '@angular/core'; +import { Directive, HostBinding, HostListener, Input, Output, EventEmitter } from '@angular/core'; import { Router } from './router'; /** @@ -25,6 +25,8 @@ export class LinkTo { this._updateHref(); } + @Output() hrefUpdated: EventEmitter = new EventEmitter(); + private _href: string; private _query: string | Object; @@ -47,6 +49,7 @@ export class LinkTo { let path = this._cleanUpHref(this._href); this.linkHref = this._router.prepareExternalUrl(path, this._query); + this.hrefUpdated.emit(this.linkHref); } /** diff --git a/spec/link-active.spec.ts b/spec/link-active.spec.ts index 3477de7..6accc60 100644 --- a/spec/link-active.spec.ts +++ b/spec/link-active.spec.ts @@ -6,7 +6,9 @@ import { iit, async, inject, - expect + expect, + fakeAsync, + tick } from '@angular/core/testing'; import { MockLocationStrategy, SpyLocation } from '@angular/common/testing'; import { TestComponentBuilder } from '@angular/compiler/testing'; @@ -24,7 +26,10 @@ import { ROUTER_PROVIDERS, Router } from '../lib/router'; template: '', directives: [LinkTo, LinkActive] }) -class TestComponent {} +class TestComponent { + public link1: string; + public link2: string; +} const compile = (tcb: TestComponentBuilder, template: string = '') => { return tcb @@ -73,6 +78,36 @@ describe('Link Active', () => { }); }))); + it('should react to changes to the linkTo parameter', fakeAsync(inject([TestComponentBuilder, Router], (tcb, router$) => { + router$.go('/page'); + + return compile(tcb, ` + Page + Other`) + .then((fixture) => { + let component = fixture.componentInstance as TestComponent; + let compiled = fixture.debugElement.nativeElement; + let linkPage: Element = compiled.querySelector('a#page'); + let linkOther: Element = compiled.querySelector('a#other'); + + component.link1 = '/page'; + component.link2 = '/other'; + fixture.detectChanges(); + tick(); + + expect(linkPage.getAttribute('class')).toEqual('active'); + expect(linkOther.getAttribute('class')).not.toEqual('active'); + + component.link1 = '/other'; + component.link2 = '/page'; + fixture.detectChanges(); + tick(); + + expect(linkPage.getAttribute('class')).not.toEqual('active'); + expect(linkOther.getAttribute('class')).toEqual('active'); + }); + }))); + it('should support multiple classes on the active element', async(inject([TestComponentBuilder, Router], (tcb, router$) => { router$.next({ path: '/page'