0

I have a problem closing my matDialog of Angular Material in my ngDoCheck() function.

EDIT : StackBlitz HERE

Explanation :

On my main page, I have a button that opens a matDialog to extract or not data into an Excel file. In my matDialog, I have two buttons. One to close the matDialog and another which starts the function extractWallet(), to recover the data in database.

In this function, I disable my buttons with the waitExtraction variable and I disable the closure of the matDialog until the extraction is complete.

My loadInvoiceExtractionXXX() methods allow me to recover my database data and once the data is received, completedExtraction set to [false, false, false] is set to true on the index (0, 1 or 2) corresponding to the function.

In my ngOnInit() the data is received via an Observer and save in a local variable. The new data is thus received after the execution of loadInvoiceExtractionXXX() in extractWallet().

My problem happens here. I have a ngDoCheck() function that checks when all three data are received with this.completedExtraction=[true, true, true]. My Excel file is well created and uploaded to the browser. Extraction is now complete, I now want to close my matDialog with this.dialogRef.close().

But the following error is displayed : enter image description here

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

Previous value: '@dialogContainer: enter'. Current value: '@dialogContainer: exit'.

I tried to close my matDialog in ngAfterViewChecked(), but the same error is displayed. If anyone has a solution to offer me to close my matDialog after the extraction, I will be interested.

I put my code at your disposal :

export class ExtractWalletComponent implements OnInit, DoCheck, OnDestroy {

  private subscription$: Subscription = new Subscription();

  selectedWallet: Wallet;
  waitExtraction: boolean;
  completedExtraction: boolean[];

  invoiceProduct: InvoiceExtractionProduct[];
  invoiceSupport: InvoiceExtractionSupport[];
  invoiceTraining: InvoiceExtractionTraining[];

  constructor(public dialogRef: MatDialogRef<ExtractWalletComponent>,
              public storeWallets: WalletsComponentStore,
              public snackBar: MatSnackBar) { }

  ngOnInit(): void {
    this.waitExtraction = false;
    this.completedExtraction = [false, false, false];

    this.subscription$.add(this.storeWallets.selectedWallet$.subscribe(wallet => this.selectedWallet = wallet));
    this.subscription$.add(this.storeWallets.invoiceExtractionProduct$.subscribe(invoiceProduct => this.invoiceProduct = invoiceProduct));
    this.subscription$.add(this.storeWallets.invoiceExtractionSupport$.subscribe(invoiceSupport => this.invoiceSupport = invoiceSupport));
    this.subscription$.add(this.storeWallets.invoiceExtractionTraining$.subscribe(invoiceTraining => this.invoiceTraining = invoiceTraining));
  }

  ngDoCheck(): void {
    if(this.completedExtraction[0] == true && this.completedExtraction[1] == true && this.completedExtraction[2] == true) {
      this.completedExtraction[0] = false;
      this.completedExtraction[1] = false;
      this.completedExtraction[2] = false;
      this.dialogRef.disableClose = false;

      const wb: XLSX.WorkBook = XLSX.utils.book_new();
      const ws1: XLSX.WorkSheet = XLSX.utils.json_to_sheet(this.invoiceProduct);
      const ws2: XLSX.WorkSheet = XLSX.utils.json_to_sheet(this.invoiceSupport);
      const ws3: XLSX.WorkSheet = XLSX.utils.json_to_sheet(this.invoiceTraining);

      XLSX.writeFile(wb, this.selectedWallet.user.num_seller + '_' + this.selectedWallet.period.month +
                          '_' + this.selectedWallet.period.year + '_' + this.selectedWallet.id + '.xlsx');

      this.dialogRef.close(); //<-- ERROR
    }
  }

  ngOnDestroy(): void {
    this.subscription$.unsubscribe();
    this.storeWallets.clearExtraction();
  }

  extractWallet(): void {
    this.waitExtraction = true;
    this.dialogRef.disableClose = true;
    this.snackBar.open('Extraction en cours', 'OK', { duration: 5000 });

    this.storeWallets.loadInvoiceExtractionProduct(this.selectedWallet).subscribe(
      res => {
        if(res) { this.completedExtraction[0] = true;}
      },
      err => { console.error(err); this.dialogRef.close(); this.snackBar.open('Echec de l\'extraction', 'OK', { duration: 2000 }); }
    );

    this.storeWallets.loadInvoiceExtractionSupport(this.selectedWallet).subscribe(
      res => {
        if(res) { this.completedExtraction[1] = true; }
      },
      err => { console.error(err); this.dialogRef.close(); this.snackBar.open('Echec de l\'extraction', 'OK', { duration: 2000 }); }
    );

    this.storeWallets.loadInvoiceExtractionTraining(this.selectedWallet).subscribe(
      res => {
        if(res) { this.completedExtraction[2] = true; }
      },
      err => { console.error(err); this.dialogRef.close(); this.snackBar.open('Echec de l\'extraction', 'OK', { duration: 2000 }); }
    );
  }
}

Thank you in advance for your help.

PS: Sorry for my english (Google translation).

Quentin
  • 1,865
  • 2
  • 24
  • 40

1 Answers1

1

Well, the problem is exactly what it tells you: You are changing something during a change detection cycle. You need to put the change outside the cycle, for example like this:

ngDoCheck(): void {
   if(this.completedExtraction[0] == true && this.completedExtraction[1] == true && this.completedExtraction[2] == true) {
      // omitted

      // Run after change detection
      setTimeout(() => this.dialogRef.close());

   }
}

But I think the main problem is that you are absuing the ngDoCheck lifecycle hook. Why don't you just react to the Observables?

combineLatest([
   this.storeWallets.loadInvoiceExtractionProduct(this.selectedWallet)
      .pipe(/* Add your catchError logic for only this observable */),
   this.storeWallets.loadInvoiceExtractionSupport(this.selectedWallet),
   this.storeWallets.loadInvoiceExtractionTraining(this.selectedWallet),
]).pipe(
   filter(results => results.every(res => !!res))
).subscribe(results => {
   // Called when all 3 returned positively
})
pascalpuetz
  • 5,238
  • 1
  • 13
  • 26