Skip to content

Commit d5d6ec8

Browse files
author
Alexander Vakrilov
authored
Merge pull request #383 from NativeScript/nsRouterLinkActive
nsRouterLinkDirective
2 parents 48f15e0 + 9f4cd1e commit d5d6ec8

File tree

8 files changed

+154
-27
lines changed

8 files changed

+154
-27
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import {AfterContentInit, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer} from '@angular/core';
2+
import {Subscription} from 'rxjs/Subscription';
3+
4+
import {NavigationEnd, Router} from '@angular/router';
5+
import {UrlTree, containsTree} from '@angular/router/src/url_tree';
6+
7+
import {NSRouterLink} from './ns-router-link';
8+
9+
10+
/**
11+
* The NSRouterLinkActive directive lets you add a CSS class to an element when the link's route
12+
* becomes active.
13+
*
14+
* Consider the following example:
15+
*
16+
* ```
17+
* <a [nsRouterLink]="/user/bob" [nsRouterLinkActive]="active-link">Bob</a>
18+
* ```
19+
*
20+
* When the url is either '/user' or '/user/bob', the active-link class will
21+
* be added to the component. If the url changes, the class will be removed.
22+
*
23+
* You can set more than one class, as follows:
24+
*
25+
* ```
26+
* <a [nsRouterLink]="/user/bob" [nsRouterLinkActive]="class1 class2">Bob</a>
27+
* <a [nsRouterLink]="/user/bob" [nsRouterLinkActive]="['class1', 'class2']">Bob</a>
28+
* ```
29+
*
30+
* You can configure NSRouterLinkActive by passing `exact: true`. This will add the classes
31+
* only when the url matches the link exactly.
32+
*
33+
* ```
34+
* <a [nsRouterLink]="/user/bob" [nsRouterLinkActive]="active-link" [nsRouterLinkActiveOptions]="{exact:
35+
* true}">Bob</a>
36+
* ```
37+
*
38+
* Finally, you can apply the NSRouterLinkActive directive to an ancestor of a RouterLink.
39+
*
40+
* ```
41+
* <div [nsRouterLinkActive]="active-link" [nsRouterLinkActiveOptions]="{exact: true}">
42+
* <a [nsRouterLink]="/user/jim">Jim</a>
43+
* <a [nsRouterLink]="/user/bob">Bob</a>
44+
* </div>
45+
* ```
46+
*
47+
* This will set the active-link class on the div tag if the url is either '/user/jim' or
48+
* '/user/bob'.
49+
*
50+
* @stable
51+
*/
52+
@Directive({ selector: '[nsRouterLinkActive]' })
53+
export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
54+
@ContentChildren(NSRouterLink) links: QueryList<NSRouterLink>;
55+
56+
private classes: string[] = [];
57+
private subscription: Subscription;
58+
59+
@Input() private nsRouterLinkActiveOptions: { exact: boolean } = { exact: false };
60+
61+
constructor(private router: Router, private element: ElementRef, private renderer: Renderer) {
62+
this.subscription = router.events.subscribe(s => {
63+
if (s instanceof NavigationEnd) {
64+
this.update();
65+
}
66+
});
67+
}
68+
69+
ngAfterContentInit(): void {
70+
this.links.changes.subscribe(s => this.update());
71+
this.update();
72+
}
73+
74+
@Input("nsRouterLinkActive")
75+
set nsRouterLinkActive(data: string[] | string) {
76+
if (Array.isArray(data)) {
77+
this.classes = <any>data;
78+
} else {
79+
this.classes = data.split(' ');
80+
}
81+
}
82+
83+
ngOnChanges(changes: {}): any { this.update(); }
84+
ngOnDestroy(): any { this.subscription.unsubscribe(); }
85+
86+
private update(): void {
87+
if (!this.links) return;
88+
89+
const currentUrlTree = this.router.parseUrl(this.router.url);
90+
const isActiveLinks = this.reduceList(currentUrlTree, this.links);
91+
this.classes.forEach(
92+
c => this.renderer.setElementClass(
93+
this.element.nativeElement, c, isActiveLinks));
94+
}
95+
96+
private reduceList(currentUrlTree: UrlTree, q: QueryList<any>): boolean {
97+
return q.reduce(
98+
(res: boolean, link: NSRouterLink) =>
99+
res || containsTree(currentUrlTree, link.urlTree, this.nsRouterLinkActiveOptions.exact),
100+
false);
101+
}
102+
}

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

+25-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {Directive, HostListener, Input, Optional} from '@angular/core';
1+
import {Directive, HostListener, Input, Optional, OnChanges} from '@angular/core';
22
import {NavigationExtras} from "@angular/router/src/router";
3-
import {ActivatedRoute} from '@angular/router';
3+
import {ActivatedRoute, Router, UrlTree} from '@angular/router';
44
import {routerLog} from "../trace";
55
import {PageRoute} from "./page-router-outlet";
66
import {RouterExtensions} from "./router-extensions";
@@ -23,7 +23,7 @@ import {isString} from "utils/types";
2323
* <a [nsRouterLink]="['/user']">link to user component</a>
2424
* ```
2525
*
26-
* RouterLink expects the value to be an array of path segments, followed by the params
26+
* NSRouterLink expects the value to be an array of path segments, followed by the params
2727
* for that level of routing. For instance `['/team', {teamId: 1}, 'user', {userId: 2}]`
2828
* means that we want to generate a link to `/team;teamId=1/user;userId=2`.
2929
*
@@ -34,7 +34,7 @@ import {isString} from "utils/types";
3434
* And if the segment begins with `../`, the router will go up one level.
3535
*/
3636
@Directive({ selector: '[nsRouterLink]' })
37-
export class NSRouterLink {
37+
export class NSRouterLink implements OnChanges {
3838
private commands: any[] = [];
3939
@Input() target: string;
4040
@Input() queryParams: { [k: string]: any };
@@ -43,9 +43,16 @@ export class NSRouterLink {
4343
@Input() clearHistory: boolean;
4444
@Input() pageTransition: boolean | string | NavigationTransition = true;
4545

46+
urlTree: UrlTree;
47+
4648
private usePageRoute: boolean;
4749

50+
private get currentRoute(): ActivatedRoute {
51+
return this.usePageRoute ? this.pageRoute.activatedRoute.getValue() : this.route;
52+
}
53+
4854
constructor(
55+
private router: Router,
4956
private navigator: RouterExtensions,
5057
private route: ActivatedRoute,
5158
@Optional() private pageRoute: PageRoute) {
@@ -62,14 +69,15 @@ export class NSRouterLink {
6269
}
6370
}
6471

72+
6573
@HostListener("tap")
6674
onTap() {
6775
routerLog("nsRouterLink.tapped: " + this.commands + " usePageRoute: " + this.usePageRoute + " clearHistory: " + this.clearHistory + " transition: " + JSON.stringify(this.pageTransition));
6876

69-
const currentRoute = this.usePageRoute ? this.pageRoute.activatedRoute.getValue() : this.route;
7077
const transition = this.getTransition();
78+
7179
let extras: NavigationExtras & NavigationOptions = {
72-
relativeTo: currentRoute,
80+
relativeTo: this.currentRoute,
7381
queryParams: this.queryParams,
7482
fragment: this.fragment,
7583
clearHistory: this.clearHistory,
@@ -93,7 +101,17 @@ export class NSRouterLink {
93101
return {
94102
animated: true,
95103
transition: this.pageTransition
96-
}
104+
};
97105
}
98106
}
107+
108+
ngOnChanges(changes: {}): any {
109+
this.updateUrlTree();
110+
}
111+
112+
private updateUrlTree(): void {
113+
this.urlTree = this.router.createUrlTree(
114+
this.commands,
115+
{ relativeTo: this.currentRoute, queryParams: this.queryParams, fragment: this.fragment });
116+
}
99117
}

nativescript-angular/router/ns-router.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { RouterConfig } from '@angular/router';
55
import { provideRouter, ExtraOptions } from '@angular/router/src/common_router_providers';
66

77
import {NSRouterLink} from './ns-router-link';
8+
import {NSRouterLinkActive} from './ns-router-link-active';
89
import {PageRouterOutlet} from './page-router-outlet';
910
import {NSLocationStrategy} from './ns-location-strategy';
1011
import {NativescriptPlatformLocation} from './ns-platform-location';
@@ -25,6 +26,7 @@ export const NS_ROUTER_PROVIDERS: any[] = [
2526

2627
export const NS_ROUTER_DIRECTIVES: Type[] = [
2728
NSRouterLink,
29+
NSRouterLinkActive,
2830
PageRouterOutlet
2931
];
3032

ng-sample/app/app.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ import { AnimationStatesTest } from "./examples/animation/animation-states-test"
6565
// nativeScriptBootstrap(RouterOutletAppComponent, [RouterOutletRouterProviders]);
6666
// nativeScriptBootstrap(PageRouterOutletAppComponent, [PageRouterOutletRouterProviders]);
6767
// nativeScriptBootstrap(PageRouterOutletNestedAppComponent, [PageRouterOutletNestedRouterProviders]);
68-
// nativeScriptBootstrap(ClearHistoryAppComponent, [ClearHistoryRouterProviders]);
69-
nativeScriptBootstrap(LoginAppComponent, [LoginExampleProviders]);
68+
nativeScriptBootstrap(ClearHistoryAppComponent, [ClearHistoryRouterProviders]);
69+
// nativeScriptBootstrap(LoginAppComponent, [LoginExampleProviders]);
7070

7171
// router-deprecated
7272
// nativeScriptBootstrap(NavigationTest, [NS_ROUTER_PROVIDERS_DEPRECATED]);

ng-sample/app/examples/router/clear-history-test.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ class LocationLogService {
1616
this.routerEvents$.next([...this.routerEvents$.getValue(), e.toString()]);
1717

1818
let states = this.strategy._getSatates()
19-
.map((v, i) => {
20-
return (i + "." + (v.isPageNavigation ? "[PAGE]" : "") + " \"" + v.url + "\"");
19+
.map((v, i) => {
20+
return (i + "." + (v.isPageNavigation ? "[PAGE]" : "") + " \"" + v.url + "\"");
2121
})
2222
.reverse();
2323

@@ -82,7 +82,7 @@ class SecondComponent implements OnInit, OnDestroy {
8282
})
8383
class ThirdComponent implements OnInit, OnDestroy {
8484
name = "Third";
85-
constructor(private nav: RouterExtensions) { }
85+
constructor(private nav: RouterExtensions) { }
8686
ngOnInit() { console.log("ThirdComponent - ngOnInit()"); }
8787
ngOnDestroy() { console.log("ThirdComponent - ngOnDestroy()"); }
8888
}
@@ -93,13 +93,13 @@ class ThirdComponent implements OnInit, OnDestroy {
9393
providers: [LocationLogService],
9494
template: `<page-router-outlet></page-router-outlet>`
9595
})
96-
export class ClearHistoryAppComponent {}
96+
export class ClearHistoryAppComponent { }
9797

9898
const routes: RouterConfig = [
9999
{ path: "", redirectTo: "/first", terminal: true },
100-
{ path: "first", component: FirstComponent},
101-
{ path: "second", component: SecondComponent},
102-
{ path: "third", component: ThirdComponent},
100+
{ path: "first", component: FirstComponent },
101+
{ path: "second", component: SecondComponent },
102+
{ path: "third", component: ThirdComponent },
103103
];
104104

105105
export const ClearHistoryRouterProviders = [

ng-sample/app/examples/router/clear-history.component.html

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33

44
<Button row="1" colSpan="3" text="BACK" (tap)="nav.back()" class="stretch"></Button>
55

6-
<Button col="0" row="2" [nsRouterLink]="['/first']"
6+
<Button col="0" row="2" [nsRouterLink]="['/first']" nsRouterLinkActive="active"
77
text="P1" class="stretch"></Button>
8-
<Button col="0" row="3" [nsRouterLink]="['/first']" clearHistory="true" pageTransition="false"
8+
<Button col="0" row="3" [nsRouterLink]="['/first']" nsRouterLinkActive="active" clearHistory="true" pageTransition="false"
99
text="P1[clear]" class="stretch"></Button>
1010

11-
<Button col="1" row="2" [nsRouterLink]="['/second']"
11+
<Button col="1" row="2" [nsRouterLink]="['/second']" nsRouterLinkActive="active"
1212
text="P2" class="stretch"></Button>
13-
<Button col="1" row="3" [nsRouterLink]="['/second']" clearHistory="true" pageTransition="none"
13+
<Button col="1" row="3" [nsRouterLink]="['/second']" nsRouterLinkActive="active" clearHistory="true" pageTransition="none"
1414
text="P2[clear]" class="stretch"></Button>
1515

16-
<Button col="2" row="2" [nsRouterLink]="['/third']"
16+
<Button col="2" row="2" [nsRouterLink]="['/third']" nsRouterLinkActive="active"
1717
text="P3" class="stretch"></Button>
18-
<Button col="2" row="3" [nsRouterLink]="['/third']" clearHistory="true" pageTransition="flipRight"
18+
<Button col="2" row="3" [nsRouterLink]="['/third']" nsRouterLinkActive="active" clearHistory="true" pageTransition="flipRight"
1919
text="P3[clear]" class="stretch"></Button>
2020

2121
<GridLayout colSpan="3" row="5">

ng-sample/app/examples/router/router-outlet-test.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class FirstComponent implements OnInit, OnDestroy {
1414
ngOnInit() {
1515
console.log("FirstComponent - ngOnInit()");
1616
}
17-
17+
1818
ngOnDestroy() {
1919
console.log("FirstComponent - ngOnDestroy()");
2020
}
@@ -50,9 +50,9 @@ class SecondComponent implements OnInit, OnDestroy {
5050
template: `
5151
<StackLayout>
5252
<StackLayout class="nav">
53-
<Button text="First" nsRouterLink="/"></Button>
54-
<Button text="Second(1)" nsRouterLink="/second/1"></Button> <!-- Both work -->
55-
<Button text="Second(2)" [nsRouterLink]="['/second', '2' ]"></Button> <!-- Both work -->
53+
<Button text="First" nsRouterLinkActive="active" nsRouterLink="/first"></Button>
54+
<Button text="Second(1)" nsRouterLinkActive="active" nsRouterLink="/second/1"></Button> <!-- Both work -->
55+
<Button text="Second(2)" nsRouterLinkActive="active" [nsRouterLink]="['/second', '2' ]"></Button> <!-- Both work -->
5656
</StackLayout>
5757
5858
<router-outlet></router-outlet>
@@ -64,7 +64,8 @@ export class RouterOutletAppComponent {
6464

6565

6666
const routes: RouterConfig = [
67-
{ path: "", component: FirstComponent},
67+
{ path: "", redirectTo: "/first", terminal: true },
68+
{ path: "first", component: FirstComponent },
6869
{ path: "second/:id", component: SecondComponent },
6970
];
7071

ng-sample/app/examples/router/styles.css

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
horizontal-align: center;
2323
}
2424

25+
.active {
26+
color: orangered;
27+
}
28+
2529
.router-link-active {
2630
background-color: lightcoral;
2731
}

0 commit comments

Comments
 (0)