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
44import { NavigationEnd , Router , UrlTree } from '@angular/router' ;
55import { containsTree } from './private-imports/router-url-tree' ;
66
77import { 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} )
5556export 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}
0 commit comments