19

Aside from using IdleJS and ngDoCheck(), how can we detect user inactivity in Angular 5?

Thanks

Artanis Zeratul
  • 963
  • 2
  • 14
  • 40
  • https://stackoverflow.com/questions/54925361/how-to-give-session-idle-timeout-in-angular-6/61773199#61773199 – jagjeet May 13 '20 at 11:22

4 Answers4

49

You could try with this :

export class AppComponent {

  userActivity;
  userInactive: Subject<any> = new Subject();

  constructor() {
    this.setTimeout();
    this.userInactive.subscribe(() => console.log('user has been inactive for 3s'));
  }

  setTimeout() {
    this.userActivity = setTimeout(() => this.userInactive.next(undefined), 3000);
  }

  @HostListener('window:mousemove') refreshUserState() {
    clearTimeout(this.userActivity);
    this.setTimeout();
  }
}

Seems to work in this stackblitz : open the console, don't move your mouse for 3 seconds : you see the message.

Refresh the page, move your mouse on the preview (right side) for a couple of seconds : the message doesn't pop until you stop for 3s.

You can obviously export that into a service, because as you can see, I'm using only a class to do that.

  • Well you can try with the blitz ... But sure, keep me updated –  Aug 02 '18 at 13:36
  • 2
    it works. I have just tried. awesome answer and work you have there! thank you so much! – Artanis Zeratul Aug 02 '18 at 14:15
  • I want to use a timer that runs a decreased time like activity check for 10 mins after 9 min timer should run decreasing the time but without any NPM – umang naik Apr 27 '20 at 02:45
  • 3
    Remember that this assumes the user is using a mouse. That might not work great for mobile users, or for those using screen readers or just keyboards. – StriplingWarrior Sep 03 '20 at 23:16
  • If using small idle time values (such as a few seconds), the code above may be run outside of Angular Zone, so that it doesn't trigger changeDetection too often. – Chris Nov 28 '20 at 13:03
  • Great solution! Thank you very much! ;) – Isabella Monza Apr 21 '21 at 14:59
11

Service that solves this problem (instead of a component as the accepted answer), handles touch, type and mouse. Emits an event N seconds after the user has been idle, and when he 'wakes up'. Works on Angular 11:

import { Injectable } from "@angular/core";
import { fromEvent, Subject } from "rxjs";

@Injectable({
    providedIn: 'root',
})
export class IdleService {

  public idle$: Subject<boolean> = new Subject();
  public wake$: Subject<boolean> = new Subject();

  isIdle = false;
  private idleAfterSeconds = 10;
  private countDown;

  constructor() {
    // Setup events
    fromEvent(document, 'mousemove').subscribe(() => this.onInteraction());
    fromEvent(document, 'touchstart').subscribe(() => this.onInteraction());
    fromEvent(document, 'keydown').subscribe(() => this.onInteraction());
  }

  onInteraction() {
    // Is idle and interacting, emit Wake
    if (this.isIdle) {
      this.isIdle = false;
      this.wake$.next(true);
    }

    // User interaction, reset start-idle-timer
    clearTimeout(this.countDown);
    this.countDown = setTimeout(() => {
      // Countdown done without interaction - emit Idle
      this.isIdle = true;
      this.idle$.next(true);
    }, this.idleAfterSeconds * 1_000)
  }
}

Usage, inject in constructor and use:

constructor(private idleService: IdleService) {  
  idleService.idle$.subscribe(s => console.log('im idle, zzz'));
  idleService.wake$.subscribe(s => console.log('im awake!'));
}
rmcsharry
  • 5,363
  • 6
  • 65
  • 108
BobbyTables
  • 4,481
  • 1
  • 31
  • 39
  • 3
    Might be adequate to change `Subject` to `BehaviorSubject` with an initial value that represents an 'awake' state upon app start, to prevent a false 'idle' state in the subscribers of `idle$` and `wake$` when the app just starts. – Lorraine R. Nov 14 '21 at 10:21
  • 2
    Nice addition to your solution would be to make the service get the idle duration through the constructor (passed in) rather then have it hard coded. Otherwise great work – krul Dec 30 '21 at 12:52
  • 1
    Nice suggestion. – Dekim Feb 10 '22 at 02:33
5

This solution worked fine for me. the problem I faced with the accepted answer is, it only focuses on mouse events and no keyboard keypress events

    userActivity;
    userInactive: Subject<any> = new Subject();

    constructor( ) {
        
        this.setTimeout();
        this.userInactive.subscribe(() => {
        this.router.navigate(['/auth/lock']);
        });
       
        }
  • These events check if user is idle for a specific time and then subscribe to a different ( lock ) screen

     keyPress(event: KeyboardEvent): void {
         clearTimeout(this.userActivity);
         this.setTimeout();
     }
     setTimeout(): void {
         this.userActivity = setTimeout(() => this.userInactive.next(undefined), 900000);
     }
    
     @HostListener('window:mousemove') refreshUserState() {
         clearTimeout(this.userActivity);
         this.setTimeout();
     }
    
danish ali
  • 132
  • 1
  • 5
  • 1
    I like that you take into account the keyboard activity and not just mouseevents. FYI I had to add "@HostListener('document:keydown', ['$event'])" to the keyPress event. I also used keydown instead of keypress based on the information in: https://stackoverflow.com/questions/37362488/how-can-i-listen-for-keypress-event-on-the-whole-page/37363257 – Thomas Jahncke Jan 14 '21 at 13:26
  • 1
    I am also adding touchEvent for mobile device uses: @HostListener('touch', ['$event']) touchEvent() ... – Thomas Jahncke Jan 14 '21 at 13:37
  • 2
    @ThomasJahncke thanks a lot. it really gave me an insight :) – danish ali Jan 15 '21 at 10:17
2

A possible solution is to use this package angular-user-idle

I have not used it in real application but might be slightly more robust than the solution proposed in the previous answer.

Julien Jacobs
  • 2,561
  • 1
  • 24
  • 34