Skip to content

Commit 365fd50

Browse files
atscottdylhunn
authored andcommitted
fix(router): RouterLinkActive will always remove active classes when links are not active (#54982)
Previously, `RouterLinkActive` would only add or remove the active classes when its active state changed. This means that if you accidentally add one of the active classes to the static class attribute, it won't get removed until the link becomes active and then deactives (because the class is added at creation time and never removed until the `RouterLinkActive` state changes from active to inactive). fixes #54978 PR Close #54982
1 parent d1d9f55 commit 365fd50

File tree

2 files changed

+47
-17
lines changed

2 files changed

+47
-17
lines changed

packages/router/src/directives/router_link_active.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -210,28 +210,30 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
210210

211211
private update(): void {
212212
if (!this.links || !this.router.navigated) return;
213+
213214
queueMicrotask(() => {
214215
const hasActiveLinks = this.hasActiveLinks();
215-
if (this._isActive !== hasActiveLinks) {
216-
this._isActive = hasActiveLinks;
217-
this.cdr.markForCheck();
218-
this.classes.forEach((c) => {
219-
if (hasActiveLinks) {
220-
this.renderer.addClass(this.element.nativeElement, c);
221-
} else {
222-
this.renderer.removeClass(this.element.nativeElement, c);
223-
}
224-
});
225-
if (hasActiveLinks && this.ariaCurrentWhenActive !== undefined) {
226-
this.renderer.setAttribute(
227-
this.element.nativeElement,
228-
'aria-current',
229-
this.ariaCurrentWhenActive.toString(),
230-
);
216+
this.classes.forEach((c) => {
217+
if (hasActiveLinks) {
218+
this.renderer.addClass(this.element.nativeElement, c);
231219
} else {
232-
this.renderer.removeAttribute(this.element.nativeElement, 'aria-current');
220+
this.renderer.removeClass(this.element.nativeElement, c);
233221
}
222+
});
223+
if (hasActiveLinks && this.ariaCurrentWhenActive !== undefined) {
224+
this.renderer.setAttribute(
225+
this.element.nativeElement,
226+
'aria-current',
227+
this.ariaCurrentWhenActive.toString(),
228+
);
229+
} else {
230+
this.renderer.removeAttribute(this.element.nativeElement, 'aria-current');
231+
}
234232

233+
// Only emit change if the active state changed.
234+
if (this._isActive !== hasActiveLinks) {
235+
this._isActive = hasActiveLinks;
236+
this.cdr.markForCheck();
235237
// Emit on isActiveChange after classes are updated
236238
this.isActiveChange.emit(hasActiveLinks);
237239
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {Component} from '@angular/core';
9+
import {TestBed} from '@angular/core/testing';
10+
import {Router, RouterLink, RouterLinkActive, provideRouter} from '@angular/router';
11+
12+
describe('RouterLinkActive', () => {
13+
it('removes initial active class even if never active', async () => {
14+
@Component({
15+
standalone: true,
16+
imports: [RouterLinkActive, RouterLink],
17+
template: '<a class="active" routerLinkActive="active" routerLink="/abc123"></a>',
18+
})
19+
class MyCmp {}
20+
21+
TestBed.configureTestingModule({providers: [provideRouter([{path: '**', children: []}])]});
22+
const fixture = TestBed.createComponent(MyCmp);
23+
fixture.autoDetectChanges();
24+
await TestBed.inject(Router).navigateByUrl('/');
25+
await fixture.whenStable();
26+
expect(Array.from(fixture.nativeElement.querySelector('a').classList)).toEqual([]);
27+
});
28+
});

0 commit comments

Comments
 (0)