Angular User Session Timeout

Home / Angular User Session Timeout

A long time ago, I blogged about a service that I used in AngularJS to let the user know that their session is about to expire and that they would be logged out if they didn’t take action. I needed to recreate this functionality in Angular for my latest swath of applications.


Dealing with timers in Angular can be significantly different when using React components and subscriptions. The basic premises remains the same, though. We’ll have a service with a timer that will provide a Subject to which consumers of the service can subscribe. A React Subject provides an easy mechanism to trigger a “next” subscription to alert consumers that the timer has expired. The new service, which I am simply calling IdleTimeoutService, will provide the same methods as the old AngularJS service: startTimer, resetTimer, etc.

import { Injectable } from '@angular/core';
import { Observable, Subject, Subscription, BehaviorSubject} from 'rxjs/Rx';

@Injectable()
export class IdleTimeoutService {
    private _count: number = 0;
    private _serviceId: string = 'idleTimeoutSvc-' + Math.floor(Math.random() * 10000);
    private _timeoutSeconds: number = 5;
    private timerSubscription: Subscription;
    private timer: Observable<number>;
    private resetOnTrigger: boolean = false;
    public timeoutExpired: Subject<number> = new Subject<number>();

    constructor() {
        console.log("Constructed idleTimeoutService " + this._serviceId);

        this.timeoutExpired.subscribe(n => {
            console.log("timeoutExpired subject next.. " + n.toString())
        });

        this.startTimer();
    }

The timer variable is our actual Observable that we use internally. You can see below that we use React’s timer observable. We’ll have it trigger in five seconds and subscribe to it. Once it is triggered, we call our handler. The “startTimer” method sets up our subscriptions.

    public startTimer() {
        if (this.timerSubscription) {
            this.timerSubscription.unsubscribe();
        }

        this.timer = Observable.timer(this._timeoutSeconds * 1000);
        this.timerSubscription = this.timer.subscribe(n => {
            this.timerComplete(n);
        });
    }

    public stopTimer() {
        this.timerSubscription.unsubscribe();
    }

    public resetTimer() {
        if (this.timerSubscription) {
            this.timerSubscription.unsubscribe();
        }

        this.timer = Observable.timer(this._timeoutSeconds * 1000);
        this.timerSubscription = this.timer.subscribe(n => {
            this.timerComplete(n);
        });
    }

Our timer handler will call the “next” method on our Subject to which others may be subscribed. This provides a nice eventing mechanism. Optionally, we can reset the timer after trigger. However, for most of my use cases, I’ll be resetting this manually.

    private timerComplete(n: number) {
        this.timeoutExpired.next(++this._count);

        if (this.resetOnTrigger) {
            this.startTimer();
        }
    }
}

To consume the service, the IdleTimerService is injected as needed. Then, we can subscribe to its exposed/public Subject.

The basic subscription is show in the code snippet.

ngOnInit() {
    this.startCounter();
    this._idleTimerSubscription = this.idleTimeoutSvc.timeoutExpired.subscribe(res => {
      var modalPromise = this.dialogSvc.open("Session Expiring!", "Your session is about to expire. Do you need more time?",
          true, "Yes", "No");
      var newObservable = Observable.fromPromise(modalPromise);
      newObservable.subscribe(
          (res) => {
              if (res === true) {
                  console.log("Extending session...");
                  this._status = "Session was extended.";
                  this.idleTimeoutSvc.resetTimer();
                  this.startCounter();
                  this.changeRef.markForCheck();
                  
              } else {
                  console.log("Not extending session...");
                  this._status = "Session was not extended.";
                  this.changeRef.markForCheck();
              }
          },
          (reason) => {
              console.log("Dismissed " + reason);
              this._status = "Session was not extended.";
              this.changeRef.markForCheck();
          }
      );
    });
  }

In the demo plunker below, I have a secondary counter within a component, also using an RxJS timer, to illustrate that the subject does emit to subscribers every five seconds. I then use a dialog service to display a modal. The modal itself returns a promise result. This simulates the concept of extending a user’s session if they click “Yes” or not extending it if they click “No.”

While I didn’t illustrate it, this becomes the starting point for hooking into Http interceptors to access a heartbeat API, or some other endpoint, to extend the user’s session at the server level (or cookie refresh). All in all, timers and observables are pretty fun to play around with in Angular.

Leave a Reply