fix(router): routerLinkActive should not throw when not initialized (#13273)
Fixes #13270 PR Close #13273
This commit is contained in:
parent
1a92e3d406
commit
e8ea741039
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer} from '@angular/core';
|
import {AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer, SimpleChanges} from '@angular/core';
|
||||||
import {Subscription} from 'rxjs/Subscription';
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
|
|
||||||
import {NavigationEnd, Router} from '../router';
|
import {NavigationEnd, Router} from '../router';
|
||||||
|
@ -14,6 +14,7 @@ import {NavigationEnd, Router} from '../router';
|
||||||
import {RouterLink, RouterLinkWithHref} from './router_link';
|
import {RouterLink, RouterLinkWithHref} from './router_link';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @whatItDoes Lets you add a CSS class to an element when the link's route becomes active.
|
* @whatItDoes Lets you add a CSS class to an element when the link's route becomes active.
|
||||||
*
|
*
|
||||||
|
@ -89,10 +90,13 @@ export class RouterLinkActive implements OnChanges,
|
||||||
|
|
||||||
private classes: string[] = [];
|
private classes: string[] = [];
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
private active: boolean = false;
|
||||||
|
|
||||||
@Input() routerLinkActiveOptions: {exact: boolean} = {exact: false};
|
@Input() routerLinkActiveOptions: {exact: boolean} = {exact: false};
|
||||||
|
|
||||||
constructor(private router: Router, private element: ElementRef, private renderer: Renderer) {
|
constructor(
|
||||||
|
private router: Router, private element: ElementRef, private renderer: Renderer,
|
||||||
|
private cdr: ChangeDetectorRef) {
|
||||||
this.subscription = router.events.subscribe(s => {
|
this.subscription = router.events.subscribe(s => {
|
||||||
if (s instanceof NavigationEnd) {
|
if (s instanceof NavigationEnd) {
|
||||||
this.update();
|
this.update();
|
||||||
|
@ -100,7 +104,7 @@ export class RouterLinkActive implements OnChanges,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get isActive(): boolean { return this.hasActiveLink(); }
|
get isActive(): boolean { return this.active; }
|
||||||
|
|
||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
this.links.changes.subscribe(_ => this.update());
|
this.links.changes.subscribe(_ => this.update());
|
||||||
|
@ -110,21 +114,24 @@ export class RouterLinkActive implements OnChanges,
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set routerLinkActive(data: string[]|string) {
|
set routerLinkActive(data: string[]|string) {
|
||||||
this.classes = (Array.isArray(data) ? data : data.split(' ')).filter(c => !!c);
|
const classes = Array.isArray(data) ? data : data.split(' ');
|
||||||
|
this.classes = classes.filter(c => !!c);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: {}): void { this.update(); }
|
ngOnChanges(changes: SimpleChanges): void { this.update(); }
|
||||||
ngOnDestroy(): void { this.subscription.unsubscribe(); }
|
ngOnDestroy(): void { this.subscription.unsubscribe(); }
|
||||||
|
|
||||||
private update(): void {
|
private update(): void {
|
||||||
if (!this.links || !this.linksWithHrefs || !this.router.navigated) return;
|
if (!this.links || !this.linksWithHrefs || !this.router.navigated) return;
|
||||||
|
const hasActiveLinks = this.hasActiveLinks();
|
||||||
|
|
||||||
const isActive = this.hasActiveLink();
|
// react only when status has changed to prevent unnecessary dom updates
|
||||||
this.classes.forEach(c => {
|
if (this.active !== hasActiveLinks) {
|
||||||
if (c) {
|
this.active = hasActiveLinks;
|
||||||
this.renderer.setElementClass(this.element.nativeElement, c, isActive);
|
this.classes.forEach(
|
||||||
|
c => this.renderer.setElementClass(this.element.nativeElement, c, hasActiveLinks));
|
||||||
|
this.cdr.detectChanges();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
|
private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
|
||||||
|
@ -132,7 +139,7 @@ export class RouterLinkActive implements OnChanges,
|
||||||
router.isActive(link.urlTree, this.routerLinkActiveOptions.exact);
|
router.isActive(link.urlTree, this.routerLinkActiveOptions.exact);
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasActiveLink(): boolean {
|
private hasActiveLinks(): boolean {
|
||||||
return this.links.some(this.isLinkActive(this.router)) ||
|
return this.links.some(this.isLinkActive(this.router)) ||
|
||||||
this.linksWithHrefs.some(this.isLinkActive(this.router));
|
this.linksWithHrefs.some(this.isLinkActive(this.router));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2096,6 +2096,8 @@ describe('Integration', () => {
|
||||||
@Component({
|
@Component({
|
||||||
template: `<a routerLink="/team" routerLinkActive #rla="routerLinkActive"></a>
|
template: `<a routerLink="/team" routerLinkActive #rla="routerLinkActive"></a>
|
||||||
<p>{{rla.isActive}}</p>
|
<p>{{rla.isActive}}</p>
|
||||||
|
<span *ngIf="rla.isActive"></span>
|
||||||
|
<span [ngClass]="{'highlight': rla.isActive}"></span>
|
||||||
<router-outlet></router-outlet>`
|
<router-outlet></router-outlet>`
|
||||||
})
|
})
|
||||||
class ComponentWithRouterLink {
|
class ComponentWithRouterLink {
|
||||||
|
@ -2115,15 +2117,15 @@ describe('Integration', () => {
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const f = TestBed.createComponent(ComponentWithRouterLink);
|
const fixture = TestBed.createComponent(ComponentWithRouterLink);
|
||||||
router.navigateByUrl('/team');
|
router.navigateByUrl('/team');
|
||||||
advance(f);
|
expect(() => advance(fixture)).not.toThrow();
|
||||||
|
|
||||||
const paragraph = f.nativeElement.querySelector('p');
|
const paragraph = fixture.nativeElement.querySelector('p');
|
||||||
expect(paragraph.textContent).toEqual('true');
|
expect(paragraph.textContent).toEqual('true');
|
||||||
|
|
||||||
router.navigateByUrl('/otherteam');
|
router.navigateByUrl('/otherteam');
|
||||||
advance(f);
|
advance(fixture);
|
||||||
|
|
||||||
expect(paragraph.textContent).toEqual('false');
|
expect(paragraph.textContent).toEqual('false');
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -264,7 +264,7 @@ export declare class RouterLinkActive implements OnChanges, OnDestroy, AfterCont
|
||||||
};
|
};
|
||||||
constructor(router: Router, element: ElementRef, renderer: Renderer, cdr: ChangeDetectorRef);
|
constructor(router: Router, element: ElementRef, renderer: Renderer, cdr: ChangeDetectorRef);
|
||||||
ngAfterContentInit(): void;
|
ngAfterContentInit(): void;
|
||||||
ngOnChanges(changes: {}): void;
|
ngOnChanges(changes: SimpleChanges): void;
|
||||||
ngOnDestroy(): void;
|
ngOnDestroy(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue