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' ;
3
3
4
4
import { NavigationEnd , Router , UrlTree } from '@angular/router' ;
5
5
import { containsTree } from './private-imports/router-url-tree' ;
6
6
7
7
import { NSRouterLink } from './ns-router-link' ;
8
+ import { mergeAll } from 'rxjs/operators' ;
8
9
9
10
/**
10
11
* 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';
54
55
} )
55
56
export class NSRouterLinkActive implements OnChanges , OnDestroy , AfterContentInit {
56
57
// tslint:disable-line:max-line-length directive-class-suffix
57
- @ContentChildren ( NSRouterLink ) links : QueryList < NSRouterLink > ;
58
+ @ContentChildren ( NSRouterLink , { descendants : true } ) links : QueryList < NSRouterLink > ;
58
59
59
60
private classes : string [ ] = [ ] ;
60
- private subscription : Subscription ;
61
+ private routerEventsSubscription : Subscription ;
62
+ private linkInputChangesSubscription ?: Subscription ;
61
63
private active : boolean = false ;
62
64
63
65
@Input ( ) nsRouterLinkActiveOptions : { exact : boolean } = { exact : false } ;
64
66
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 ) => {
67
69
if ( s instanceof NavigationEnd ) {
68
70
this . update ( ) ;
69
71
}
@@ -75,8 +77,25 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
75
77
}
76
78
77
79
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
+ } ) ;
80
99
}
81
100
82
101
@Input ( 'nsRouterLinkActive' )
@@ -92,30 +111,34 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
92
111
this . update ( ) ;
93
112
}
94
113
ngOnDestroy ( ) : any {
95
- this . subscription . unsubscribe ( ) ;
114
+ this . routerEventsSubscription . unsubscribe ( ) ;
115
+ this . linkInputChangesSubscription ?. unsubscribe ( ) ;
96
116
}
97
117
98
118
private update ( ) : void {
99
119
if ( ! this . links ) {
100
120
return ;
101
121
}
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
+ } ) ;
116
139
}
117
140
118
- private reduceList ( currentUrlTree : UrlTree , q : QueryList < any > ) : boolean {
141
+ private reduceList ( currentUrlTree : UrlTree , q : QueryList < any > | Array < any > ) : boolean {
119
142
return q . reduce ( ( res : boolean , link : NSRouterLink ) => {
120
143
return res || containsTree ( currentUrlTree , link . urlTree , this . nsRouterLinkActiveOptions . exact ) ;
121
144
} , false ) ;
@@ -126,6 +149,7 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni
126
149
}
127
150
128
151
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 ) ;
130
154
}
131
155
}
0 commit comments