Skip to content

Commit

Permalink
fix(ivy): nsRouterLinkActive works on nsRouterLink (#2322)
Browse files Browse the repository at this point in the history
  • Loading branch information
edusperoni committed Mar 24, 2021
1 parent 5632898 commit 13a3562
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 27 deletions.
74 changes: 49 additions & 25 deletions nativescript-angular/router/ns-router-link-active.ts
@@ -1,10 +1,11 @@
import { AfterContentInit, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer2 } from '@angular/core';
import { Subscription } from 'rxjs';
import { AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, Optional, QueryList, Renderer2 } from '@angular/core';
import { from, of, Subscription } from 'rxjs';

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

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

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

private classes: string[] = [];
private subscription: Subscription;
private routerEventsSubscription: Subscription;
private linkInputChangesSubscription?: Subscription;
private active: boolean = false;

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

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

ngAfterContentInit(): void {
this.links.changes.subscribe(() => this.update());
this.update();
// `of(null)` is used to force subscribe body to execute once immediately (like `startWith`).
from([this.links.changes, of(null)])
.pipe(mergeAll())
.subscribe((_) => {
this.update();
this.subscribeToEachLinkOnChanges();
});
}

private subscribeToEachLinkOnChanges() {
this.linkInputChangesSubscription?.unsubscribe();
const allLinkChanges = [...this.links.toArray(), this.link].filter((link): link is NSRouterLink => !!link).map((link) => link.onChanges);
this.linkInputChangesSubscription = from(allLinkChanges)
.pipe(mergeAll())
.subscribe((link) => {
if (this.isActive !== this.isLinkActive(this.router)(link)) {
this.update();
}
});
}

@Input('nsRouterLinkActive')
Expand All @@ -92,30 +111,34 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
this.update();
}
ngOnDestroy(): any {
this.subscription.unsubscribe();
this.routerEventsSubscription.unsubscribe();
this.linkInputChangesSubscription?.unsubscribe();
}

private update(): void {
if (!this.links) {
return;
}
const hasActiveLinks = this.hasActiveLinks();
// react only when status has changed to prevent unnecessary dom updates
if (this.active !== hasActiveLinks) {
const currentUrlTree = this.router.parseUrl(this.router.url);
const isActiveLinks = this.reduceList(currentUrlTree, this.links);
this.classes.forEach((c) => {
if (isActiveLinks) {
this.renderer.addClass(this.element.nativeElement, c);
} else {
this.renderer.removeClass(this.element.nativeElement, c);
}
});
}
Promise.resolve(hasActiveLinks).then((active) => (this.active = active));
Promise.resolve().then(() => {
const hasActiveLinks = this.hasActiveLinks();
if (this.active !== hasActiveLinks) {
this.active = hasActiveLinks;
const currentUrlTree = this.router.parseUrl(this.router.url);
const links = this.link ? [...this.links.toArray(), this.link] : this.links;
const isActiveLinks = this.reduceList(currentUrlTree, links);
this.cdr.markForCheck();
this.classes.forEach((c) => {
if (isActiveLinks) {
this.renderer.addClass(this.element.nativeElement, c);
} else {
this.renderer.removeClass(this.element.nativeElement, c);
}
});
}
});
}

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

private hasActiveLinks(): boolean {
return this.links.some(this.isLinkActive(this.router));
const isActiveCheckFn = this.isLinkActive(this.router);
return (this.link && isActiveCheckFn(this.link)) || this.links.some(isActiveCheckFn);
}
}
14 changes: 12 additions & 2 deletions nativescript-angular/router/ns-router-link.ts
@@ -1,10 +1,11 @@
import { Directive, Input, ElementRef, NgZone } from '@angular/core';
import { Directive, Input, ElementRef, NgZone, OnChanges, SimpleChanges } from '@angular/core';
import { NavigationExtras } from '@angular/router';
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
import { NavigationTransition } from '@nativescript/core';
import { NativeScriptDebug } from '../trace';
import { RouterExtensions } from './router-extensions';
import { NavigationOptions } from './ns-location-utils';
import { Subject } from 'rxjs';

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

/** @internal */
onChanges = new Subject<NSRouterLink>();

private commands: any[] = [];

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

ngOnChanges(changes: SimpleChanges) {
// This is subscribed to by `RouterLinkActive` so that it knows to update when there are changes
// to the RouterLinks it's tracking.
this.onChanges.next(this);
}

@Input('nsRouterLink')
set params(data: any[] | string) {
if (Array.isArray(data)) {
Expand Down

0 comments on commit 13a3562

Please sign in to comment.