Navigation using routerLink
on an <a>
element occurs immediately, but there are situations where delaying navigation after clicking a link would be benificial
Flickering Transitions
Transitions on sliding menus can appear to flicker if they are transitioning simultaneouly with the rendering of the underlying components e.g. new pages being loaded while a menu is still transitioning off the screen. Allowing the transiiton to complete before the navigation occurs removes the chance of flickering
I wanted to have this delay functionality while still having the declarative ease of use of Angular's inbuilt routerLink
directive. I extended the current functionality with an additional @Input
for delaying the navigation by a given nubmer of milliseconds. The extended directive is available as an npm module
Extending Directives
When extending Angular directives we need to use a custom selector to differentiate between the built-in and extended versions. In this case I am using a prefix on all directives and components (which I set when initializing an Angular CLI project)
@Directive({
selector: 'a[bcRouterLink]'
})
export class RouterLinkWithHrefDelay extends RouterLinkWithHref implements OnDestroy {
To maintain the functionality of the base class RouterLinkWithHref
we have to set the RouterLinkWithHref.routerLink
input value when we recieve the bcRouterLink
input value from the template:
<a [bcRouterLink]="['/page-one']" bcRouterLinkActive="active" [navigationDelay]="250">
@Input()
set bcRouterLink(commands: any[] | string) {
this.routerLink = commands;
}
We need to override the @HostListener
click event handler of RouterLinkWithHref
, and add in our delay logic. For this I am using an Observable timer operator:
import { timer } from 'rxjs/observable/timer';
// v1.1.0 is using lettable Rxjs timer
// removed: Observable.timer(this.navigationDelay)
this.timerSubscription = timer(this.navigationDelay)
.subscribe(t => {
this.timerSubscription.unsubscribe();
super.onClick(button, ctrlKey, metaKey, shiftKey);
});
The Observable.timer
completes after it's first event if no period
argument is supplied - I'm calling unsubscribe here because..... see Ben Lesh's article on not calling unsubscribe too often. I just realised that unsubscribe isn't needed while writing this article - next release!! 😉
// Observable.timer function signature
timer(initialDelay: number | Date, period: number, scheduler: Scheduler): Observable
The this.timerSubscription
is also invoked if necessary in ngOnDestroy
, for the case that the <a>
element is removed from the view before the timer completes (this scenario would probably be very rare - but it can be emulated using fakeasync
tests)
Active Link
The routerLinkActive directive (easily confused with an @Input of the routerLink
directive) also needs to be extended to work alongside the extended RouterLinkWithHref
.
Remember to look carefully at the source files directly and not just the
.d.ts
definitions when extending Angular components
RouterLinkActive
queries the template for a list of anchor elements with directives of type RouterLinkWithHref
. We need to extend RouterLinkActive
and query for the extended component:
@ContentChildren(RouterLinkWithHref, {descendants: true})
linksWithHrefs: QueryList<RouterLinkWithHref>;
// becomes ->
@ContentChildren(RouterLinkWithHrefDelay, { descendants: true })
linksWithHrefs: QueryList<RouterLinkWithHrefDelay>;
Usage
app.component.html
<div>
<a [bcRouterLink]="['/page-one']" bcRouterLinkActive="active" [navigationDelay]="250">
Page One
</a>
<a bcRouterLink="/page-two" bcRouterLinkActive="active" [navigationDelay]="250">
Page Two
</a>
<router-outlet></router-outlet>
</div>
app.module.ts
import { AppComponent } from './app.component';
import { RouterLinkDelayModule } from '@bcodes/ngx-routerlink-delay';
import { PageOneComponent } from './pages/page-one/page-one.component';
import { PageTwoComponent } from './pages/page-two/page-two.component';
@NgModule({
declarations: [
AppComponent,
PageOneComponent,
PageTwoComponent
],
imports: [
BrowserModule,
AppRoutingModule,
RouterLinkDelayModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
npm and GitHub
The directive is available as an npm module
npm install @bcodes/ngx-routerlink-delay
The source and tests can be seen on GitHub