Having issues with ng-idle/core onIdleEnd & Mat-Dialog

4.4k views Asked by At

There is an issue I have discovered with Ng-Idle, Material 6 nad Angular 6

"@ng-idle/core": "^6.0.0-beta.3"
"@ng-idle/keepalive": "^6.0.0-beta.3"
"@angular/core": "^6.1.9"
"@angular/cdk": "^6.4.7"
"@angular/material": "^6.4.7"

The scenario

when ever a user has gone idle, a dialog (popup) displays a countdown how long until the user is logged out of the system. If the user return prior to being logged out with mouse activity, the countdown will halt and the dialog will close/disappear.

Issue

However, in Angular 5, this feature was working fine until I upgraded to Angular 6. When ever the user returns prior to onTimeout, it fires the onIdleEnd but the dialog doesn't disappear on mouse activity. I created an Angular 6 app to replicate the issue. I am trying to determine if this is an Ng-Idle or an Angular issue.

Stackblitz with Ng-Idle

Stackblitz showing Mat-Dialog closing after 10 second countdown

Github

Has anyone come across this issue?

2

There are 2 answers

3
user3266824 On BEST ANSWER

I had the same issue. I solved it by pushing the change in angular.

First:

{ AppplicationRef } from '@angular/core';

In the constructor of your component add ChangeDetectorRef:

constructor(private appRef: ApplicationRef)

then call it in on onIdleEnd:

this.idle.onIdleEnd.subscribe(() => {
    this.showModal = false;
    this.appRef.tick();
});

StackBlitz solution.

3
scottfrenz On

Okay so I can't comment because I have no reputation yet, but I wanted to share how I worked around this. What I did is create a parent <div> element around my dialog title/content which calls a closeMe() function on click. This closeMe() calls the 'this.dialogRef.close()' function which actually does close the dialog box.

When the ng2-idle fires the onIdleEnd observable, I simulate the click on that parent div. To do this, I needed to 'inject' the Idle object into the dialog.

My dialog box component.ts file:

import { Component, OnInit, Inject, ViewChild, ElementRef } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { Idle } from '@ng-idle/core';

@Component({
  selector: 'idle-timeout-warning-modal',
  templateUrl: './idle-timeout-warning.component.html',
  styleUrls: ['./idle-timeout-warning.component.css'],
})
export class IdleIimeoutWarningComponent implements OnInit {
  private idle: Idle;

  public countdown: number;

  //Need this in order to close the dialog box on idle end. This is because, for some reason,
  //I cannot do a this.dialogRef.close() in the onIdleEnd subscription.
  @ViewChild('closeMeDiv') closeMeDiv: ElementRef;

  constructor(
    public dialogRef: MatDialogRef<IdleIimeoutWarningComponent>,
    @Inject(MAT_DIALOG_DATA) public data: any, //Data is: {"idleObj":<idle object>, "timeout":<timeoutPeriodSec (integer)>}
  ) { 
    this.idle = data.idleObj;
    this.countdown = data.timeout;
  }

  ngOnInit() { 
    this.idle.onTimeoutWarning.subscribe((countdown: number) => {
      this.countdown = countdown;
    });
    this.idle.onIdleEnd.subscribe(() => { 
      this.closeMeDiv.nativeElement.click();
    });
  }

  closeMe() {
    this.dialogRef.close();
  }
}

My dialog box html file:

<div #closeMeDiv (click)="closeMe()">
    <div mat-dialog-title>
        <h3>Please move the mouse or press any key</h3>
        <hr />
    </div>

    <div mat-dialog-content>
        <p>
            You'll be logged out in <span class="idle-label idle-label-warning">{{countdown}}</span>
            second<span *ngIf="countdown != 1">s</span>.
        </p>
    </div>
</div>

Then in the idle setup function (in another service I have, where ````this.idle``` is injected in the constructor parameters):

let idleStartSec:number = 5;
let timeoutPeriodSec:number = 5;

// sets an idle timeout - will trigger timeout period
this.idle.setIdle(idleStartSec);
// sets a timeout period. after this amount of inactivity, the user will be considered timed out.
this.idle.setTimeout(timeoutPeriodSec);
// sets the default interrupts, in this case, things like clicks, scrolls, touches to the document
this.idle.setInterrupts(DEFAULT_INTERRUPTSOURCES);

this.idle.onIdleStart.subscribe(() => { //Fires when timeout is about to start
  this.dialogRef = this.dialog.open(IdleIimeoutWarningComponent, {
    panelClass: 'modal-lg',
    data: {"idleObj":this.idle, "timeout":timeoutPeriodSec}
  });
});

this.idle.onTimeout.subscribe(() => {
  this.dialogRef.close();
  //Do other stuff here
});

It's strange that the direct call to this.dialogRef.close(); works in onTimeout but not in the onIdleEnd.

Anyway, hope this helps until the issue is fixed.