Skip to content

Commit

Permalink
feat(router): new output that would notify when link is activated
Browse files Browse the repository at this point in the history
This commit adds a new output to `routerLinkActive` directive.
Whenever, the associated link becomes active or inactive, an
event will be fired on this out with the correct status

PR Close #37284
  • Loading branch information
anandtiwary committed Aug 28, 2021
1 parent 95cfd0b commit ff004a6
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 2 deletions.
2 changes: 2 additions & 0 deletions goldens/public-api/router/router.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,8 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
// (undocumented)
readonly isActive: boolean;
// (undocumented)
readonly isActiveChange: EventEmitter<boolean>;
// (undocumented)
links: QueryList<RouterLink>;
// (undocumented)
linksWithHrefs: QueryList<RouterLinkWithHref>;
Expand Down
16 changes: 15 additions & 1 deletion packages/router/src/directives/router_link_active.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, Optional, QueryList, Renderer2, SimpleChanges} from '@angular/core';
import {AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Optional, Output, QueryList, Renderer2, SimpleChanges} from '@angular/core';
import {from, of, Subscription} from 'rxjs';
import {mergeAll} from 'rxjs/operators';

Expand Down Expand Up @@ -72,6 +72,16 @@ import {RouterLink, RouterLinkWithHref} from './router_link';
* </div>
* ```
*
* You can use the output `isActiveChange` to get notified each time the link becomes
* active or inactive.
*
* ```
* <a
* routerLink="/user/bob"
* routerLinkActive="active-link"
* (isActiveChange)="this.onRouterLinkActive($event)">Bob</a>
* ```
*
* @ngModule RouterModule
*
* @publicApi
Expand Down Expand Up @@ -99,6 +109,7 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
*/
@Input() routerLinkActiveOptions: {exact: boolean}|IsActiveMatchOptions = {exact: false};

@Output() readonly isActiveChange: EventEmitter<boolean> = new EventEmitter();

constructor(
private router: Router, private element: ElementRef, private renderer: Renderer2,
Expand Down Expand Up @@ -163,6 +174,9 @@ export class RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit
this.renderer.removeClass(this.element.nativeElement, c);
}
});

// Emit on isActiveChange after classes are updated
this.isActiveChange.emit(hasActiveLinks);
}
});
}
Expand Down
46 changes: 45 additions & 1 deletion packages/router/test/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4662,6 +4662,44 @@ describe('Integration', () => {
fixture.detectChanges(false /** checkNoChanges */);
expect(TestBed.inject(NgZone).hasPendingMicrotasks).toBe(false);
}));

it('should emit on isActiveChange output when link is activated or inactivated',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const fixture = createRoot(router, RootCmp);

router.resetConfig([{
path: 'team/:id',
component: TeamCmp,
children: [{
path: 'link',
component: DummyLinkCmp,
children: [{path: 'simple', component: SimpleCmp}, {path: '', component: BlankCmp}]
}]
}]);

router.navigateByUrl('/team/22/link;exact=true');
advance(fixture);
advance(fixture);
expect(location.path()).toEqual('/team/22/link;exact=true');

const linkComponent =
fixture.debugElement.query(By.directive(DummyLinkCmp)).componentInstance as
DummyLinkCmp;

expect(linkComponent.isLinkActivated).toEqual(true);
const nativeLink = fixture.nativeElement.querySelector('a');
const nativeButton = fixture.nativeElement.querySelector('button');
expect(nativeLink.className).toEqual('active');
expect(nativeButton.className).toEqual('active');


router.navigateByUrl('/team/22/link/simple');
advance(fixture);
expect(location.path()).toEqual('/team/22/link/simple');
expect(linkComponent.isLinkActivated).toEqual(false);
expect(nativeLink.className).toEqual('');
expect(nativeButton.className).toEqual('');
})));
});

describe('lazy loading', () => {
Expand Down Expand Up @@ -5928,15 +5966,21 @@ class AbsoluteLinkCmp {
@Component({
selector: 'link-cmp',
template:
`<router-outlet></router-outlet><a routerLinkActive="active" [routerLinkActiveOptions]="{exact: exact}" [routerLink]="['./']">link</a>
`<router-outlet></router-outlet><a routerLinkActive="active" (isActiveChange)="this.onRouterLinkActivated($event)" [routerLinkActiveOptions]="{exact: exact}" [routerLink]="['./']">link</a>
<button routerLinkActive="active" [routerLinkActiveOptions]="{exact: exact}" [routerLink]="['./']">button</button>
`
})
class DummyLinkCmp {
private exact: boolean;
public isLinkActivated?: boolean;

constructor(route: ActivatedRoute) {
this.exact = route.snapshot.paramMap.get('exact') === 'true';
}

public onRouterLinkActivated(isActive: boolean): void {
this.isLinkActivated = isActive;
}
}

@Component({selector: 'link-cmp', template: `<a [routerLink]="['/simple']">link</a>`})
Expand Down

0 comments on commit ff004a6

Please sign in to comment.