Skip to content

Commit 36eb9d3

Browse files
committed
feat(router): router-link-active CSS class support
The `[router-link]` directive now applies the `router-link-active` CSS class to the associated element whenever the link is active. Closes angular#3209
1 parent de37729 commit 36eb9d3

File tree

2 files changed

+110
-6
lines changed

2 files changed

+110
-6
lines changed

modules/angular2/src/router/router_link.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ import {Instruction, stringifyInstruction} from './instruction';
3737
@Directive({
3838
selector: '[router-link]',
3939
properties: ['routeParams: routerLink'],
40-
host: {'(^click)': 'onClick()', '[attr.href]': 'visibleHref'}
40+
host: {
41+
'(^click)': 'onClick()',
42+
'[attr.href]': 'visibleHref',
43+
'[class.router-link-active]': 'isRouteActive'
44+
}
4145
})
4246
export class RouterLink {
4347
private _routeParams: List<any>;
@@ -50,6 +54,8 @@ export class RouterLink {
5054

5155
constructor(private _router: Router, private _location: Location) {}
5256

57+
get isRouteActive(): boolean { return this._router.isRouteActive(this._navigationInstruction); }
58+
5359
set routeParams(changes: List<any>) {
5460
this._routeParams = changes;
5561
this._navigationInstruction = this._router.generate(this._routeParams);

modules/angular2/test/router/integration/router_link_spec.ts

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import {
3232
RouterOutlet,
3333
Route,
3434
RouteParams,
35-
RouteConfig
35+
RouteConfig,
36+
ROUTER_DIRECTIVES
3637
} from 'angular2/router';
3738
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
3839

@@ -50,7 +51,7 @@ export function main() {
5051
bind(Location).toClass(SpyLocation),
5152
bind(Router)
5253
.toFactory((registry, pipeline,
53-
location) => { return new RootRouter(registry, pipeline, location, AppCmp); },
54+
location) => { return new RootRouter(registry, pipeline, location, MyComp); },
5455
[RouteRegistry, Pipeline, Location])
5556
]);
5657

@@ -153,6 +154,80 @@ export function main() {
153154
}));
154155

155156

157+
describe('router-link-active CSS class', () => {
158+
it('should be added to the associated element', inject([AsyncTestCompleter], (async) => {
159+
router.config([
160+
new Route({path: '/child', component: HelloCmp, as: 'child'}),
161+
new Route({path: '/better-child', component: Hello2Cmp, as: 'better-child'})
162+
])
163+
.then((_) => compile(`<a [router-link]="['./child']" class="child-link">Child</a>
164+
<a [router-link]="['./better-child']" class="better-child-link">Better Child</a>
165+
<router-outlet></router-outlet>`))
166+
.then((_) => {
167+
var element = rootTC.nativeElement;
168+
169+
rootTC.detectChanges();
170+
171+
var link1 = DOM.querySelector(element, '.child-link');
172+
var link2 = DOM.querySelector(element, '.better-child-link');
173+
174+
expect(link1).not.toHaveCssClass('router-link-active');
175+
expect(link2).not.toHaveCssClass('router-link-active');
176+
177+
router.subscribe((_) => {
178+
rootTC.detectChanges();
179+
180+
expect(link1).not.toHaveCssClass('router-link-active');
181+
expect(link2).toHaveCssClass('router-link-active');
182+
183+
async.done();
184+
});
185+
router.navigate('/better-child');
186+
});
187+
}));
188+
189+
it('should be added to links in child routes', inject([AsyncTestCompleter], (async) => {
190+
router.config([
191+
new Route({path: '/child', component: HelloCmp, as: 'child'}),
192+
new Route({
193+
path: '/child-with-grandchild/...',
194+
component: ParentCmp,
195+
as: 'child-with-grandchild'
196+
})
197+
])
198+
.then((_) => compile(`<a [router-link]="['./child']" class="child-link">Child</a>
199+
<a [router-link]="['./child-with-grandchild/grandchild']" class="child-with-grandchild-link">Better Child</a>
200+
<router-outlet></router-outlet>`))
201+
.then((_) => {
202+
var element = rootTC.nativeElement;
203+
204+
rootTC.detectChanges();
205+
206+
var link1 = DOM.querySelector(element, '.child-link');
207+
var link2 = DOM.querySelector(element, '.child-with-grandchild-link');
208+
209+
expect(link1).not.toHaveCssClass('router-link-active');
210+
expect(link2).not.toHaveCssClass('router-link-active');
211+
212+
router.subscribe((_) => {
213+
rootTC.detectChanges();
214+
215+
expect(link1).not.toHaveCssClass('router-link-active');
216+
expect(link2).toHaveCssClass('router-link-active');
217+
218+
var link3 = DOM.querySelector(element, '.grandchild-link');
219+
var link4 = DOM.querySelector(element, '.better-grandchild-link');
220+
221+
expect(link3).toHaveCssClass('router-link-active');
222+
expect(link4).not.toHaveCssClass('router-link-active');
223+
224+
async.done();
225+
});
226+
router.navigate('/child-with-grandchild/grandchild');
227+
});
228+
}));
229+
});
230+
156231
describe('when clicked', () => {
157232

158233
var clickOnElement = function(view) {
@@ -209,8 +284,6 @@ function getHref(tc) {
209284
return DOM.getAttribute(tc.componentViewChildren[0].nativeElement, 'href');
210285
}
211286

212-
class AppCmp {}
213-
214287
@Component({selector: 'my-comp'})
215288
class MyComp {
216289
name;
@@ -238,11 +311,36 @@ class SiblingPageCmp {
238311
}
239312
}
240313

314+
@Component({selector: 'hello-cmp'})
315+
@View({template: 'hello'})
316+
class HelloCmp {
317+
}
318+
319+
@Component({selector: 'hello2-cmp'})
320+
@View({template: 'hello2'})
321+
class Hello2Cmp {
322+
}
323+
324+
@Component({selector: 'parent-cmp'})
325+
@View({
326+
template: `{ <a [router-link]="['./grandchild']" class="grandchild-link">Grandchild</a>
327+
<a [router-link]="['./better-grandchild']" class="better-grandchild-link">Better Grandchild</a>
328+
<router-outlet></router-outlet> }`,
329+
directives: ROUTER_DIRECTIVES
330+
})
331+
@RouteConfig([
332+
new Route({path: '/grandchild', component: HelloCmp, as: 'grandchild'}),
333+
new Route({path: '/better-grandchild', component: Hello2Cmp, as: 'better-grandchild'})
334+
])
335+
class ParentCmp {
336+
constructor(public router: Router) {}
337+
}
338+
241339
@Component({selector: 'book-cmp'})
242340
@View({
243341
template: `<a href="hello" [router-link]="[\'./page\', {number: 100}]">{{title}}</a> |
244342
<router-outlet></router-outlet>`,
245-
directives: [RouterLink, RouterOutlet]
343+
directives: ROUTER_DIRECTIVES
246344
})
247345
@RouteConfig([new Route({path: '/page/:number', component: SiblingPageCmp, as: 'page'})])
248346
class BookCmp {

0 commit comments

Comments
 (0)