Skip to content

Commit 13a3562

Browse files
authored
fix(ivy): nsRouterLinkActive works on nsRouterLink (#2322)
1 parent 5632898 commit 13a3562

File tree

2 files changed

+61
-27
lines changed

2 files changed

+61
-27
lines changed

nativescript-angular/router/ns-router-link-active.ts

+49-25
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { AfterContentInit, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer2 } from '@angular/core';
2-
import { Subscription } from 'rxjs';
1+
import { AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, Optional, QueryList, Renderer2 } from '@angular/core';
2+
import { from, of, Subscription } from 'rxjs';
33

44
import { NavigationEnd, Router, UrlTree } from '@angular/router';
55
import { containsTree } from './private-imports/router-url-tree';
66

77
import { NSRouterLink } from './ns-router-link';
8+
import { mergeAll } from 'rxjs/operators';
89

910
/**
1011
* The NSRouterLinkActive directive lets you add a CSS class to an element when the link"s route
@@ -54,16 +55,17 @@ import { NSRouterLink } from './ns-router-link';
5455
})
5556
export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
5657
// tslint:disable-line:max-line-length directive-class-suffix
57-
@ContentChildren(NSRouterLink) links: QueryList<NSRouterLink>;
58+
@ContentChildren(NSRouterLink, { descendants: true }) links: QueryList<NSRouterLink>;
5859

5960
private classes: string[] = [];
60-
private subscription: Subscription;
61+
private routerEventsSubscription: Subscription;
62+
private linkInputChangesSubscription?: Subscription;
6163
private active: boolean = false;
6264

6365
@Input() nsRouterLinkActiveOptions: { exact: boolean } = { exact: false };
6466

65-
constructor(private router: Router, private element: ElementRef, private renderer: Renderer2) {
66-
this.subscription = router.events.subscribe((s) => {
67+
constructor(private router: Router, private element: ElementRef, private renderer: Renderer2, private readonly cdr: ChangeDetectorRef, @Optional() private link?: NSRouterLink) {
68+
this.routerEventsSubscription = router.events.subscribe((s) => {
6769
if (s instanceof NavigationEnd) {
6870
this.update();
6971
}
@@ -75,8 +77,25 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
7577
}
7678

7779
ngAfterContentInit(): void {
78-
this.links.changes.subscribe(() => this.update());
79-
this.update();
80+
// `of(null)` is used to force subscribe body to execute once immediately (like `startWith`).
81+
from([this.links.changes, of(null)])
82+
.pipe(mergeAll())
83+
.subscribe((_) => {
84+
this.update();
85+
this.subscribeToEachLinkOnChanges();
86+
});
87+
}
88+
89+
private subscribeToEachLinkOnChanges() {
90+
this.linkInputChangesSubscription?.unsubscribe();
91+
const allLinkChanges = [...this.links.toArray(), this.link].filter((link): link is NSRouterLink => !!link).map((link) => link.onChanges);
92+
this.linkInputChangesSubscription = from(allLinkChanges)
93+
.pipe(mergeAll())
94+
.subscribe((link) => {
95+
if (this.isActive !== this.isLinkActive(this.router)(link)) {
96+
this.update();
97+
}
98+
});
8099
}
81100

82101
@Input('nsRouterLinkActive')
@@ -92,30 +111,34 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
92111
this.update();
93112
}
94113
ngOnDestroy(): any {
95-
this.subscription.unsubscribe();
114+
this.routerEventsSubscription.unsubscribe();
115+
this.linkInputChangesSubscription?.unsubscribe();
96116
}
97117

98118
private update(): void {
99119
if (!this.links) {
100120
return;
101121
}
102-
const hasActiveLinks = this.hasActiveLinks();
103-
// react only when status has changed to prevent unnecessary dom updates
104-
if (this.active !== hasActiveLinks) {
105-
const currentUrlTree = this.router.parseUrl(this.router.url);
106-
const isActiveLinks = this.reduceList(currentUrlTree, this.links);
107-
this.classes.forEach((c) => {
108-
if (isActiveLinks) {
109-
this.renderer.addClass(this.element.nativeElement, c);
110-
} else {
111-
this.renderer.removeClass(this.element.nativeElement, c);
112-
}
113-
});
114-
}
115-
Promise.resolve(hasActiveLinks).then((active) => (this.active = active));
122+
Promise.resolve().then(() => {
123+
const hasActiveLinks = this.hasActiveLinks();
124+
if (this.active !== hasActiveLinks) {
125+
this.active = hasActiveLinks;
126+
const currentUrlTree = this.router.parseUrl(this.router.url);
127+
const links = this.link ? [...this.links.toArray(), this.link] : this.links;
128+
const isActiveLinks = this.reduceList(currentUrlTree, links);
129+
this.cdr.markForCheck();
130+
this.classes.forEach((c) => {
131+
if (isActiveLinks) {
132+
this.renderer.addClass(this.element.nativeElement, c);
133+
} else {
134+
this.renderer.removeClass(this.element.nativeElement, c);
135+
}
136+
});
137+
}
138+
});
116139
}
117140

118-
private reduceList(currentUrlTree: UrlTree, q: QueryList<any>): boolean {
141+
private reduceList(currentUrlTree: UrlTree, q: QueryList<any> | Array<any>): boolean {
119142
return q.reduce((res: boolean, link: NSRouterLink) => {
120143
return res || containsTree(currentUrlTree, link.urlTree, this.nsRouterLinkActiveOptions.exact);
121144
}, false);
@@ -126,6 +149,7 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
126149
}
127150

128151
private hasActiveLinks(): boolean {
129-
return this.links.some(this.isLinkActive(this.router));
152+
const isActiveCheckFn = this.isLinkActive(this.router);
153+
return (this.link && isActiveCheckFn(this.link)) || this.links.some(isActiveCheckFn);
130154
}
131155
}

nativescript-angular/router/ns-router-link.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Directive, Input, ElementRef, NgZone } from '@angular/core';
1+
import { Directive, Input, ElementRef, NgZone, OnChanges, SimpleChanges } from '@angular/core';
22
import { NavigationExtras } from '@angular/router';
33
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
44
import { NavigationTransition } from '@nativescript/core';
55
import { NativeScriptDebug } from '../trace';
66
import { RouterExtensions } from './router-extensions';
77
import { NavigationOptions } from './ns-location-utils';
8+
import { Subject } from 'rxjs';
89

910
// Copied from "@angular/router/src/config"
1011
export type QueryParamsHandling = 'merge' | 'preserve' | '';
@@ -34,7 +35,7 @@ export type QueryParamsHandling = 'merge' | 'preserve' | '';
3435
* And if the segment begins with `../`, the router will go up one level.
3536
*/
3637
@Directive({ selector: '[nsRouterLink]' })
37-
export class NSRouterLink {
38+
export class NSRouterLink implements OnChanges {
3839
// tslint:disable-line:directive-class-suffix
3940
@Input() target: string;
4041
@Input() queryParams: { [k: string]: any };
@@ -50,6 +51,9 @@ export class NSRouterLink {
5051
@Input() pageTransition: boolean | string | NavigationTransition = true;
5152
@Input() pageTransitionDuration;
5253

54+
/** @internal */
55+
onChanges = new Subject<NSRouterLink>();
56+
5357
private commands: any[] = [];
5458

5559
constructor(private ngZone: NgZone, private router: Router, private navigator: RouterExtensions, private route: ActivatedRoute, private el: ElementRef) {}
@@ -75,6 +79,12 @@ export class NSRouterLink {
7579
});
7680
}
7781

82+
ngOnChanges(changes: SimpleChanges) {
83+
// This is subscribed to by `RouterLinkActive` so that it knows to update when there are changes
84+
// to the RouterLinks it's tracking.
85+
this.onChanges.next(this);
86+
}
87+
7888
@Input('nsRouterLink')
7989
set params(data: any[] | string) {
8090
if (Array.isArray(data)) {

0 commit comments

Comments
 (0)