Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Material Dialog & Hot Module Reload

So far, I've successfully done the following:

✓ HMR (Hot Module Reload) set up
✓ Angular (5) and Material working well
✓ Open a dialog (code snippet below)

// ...
constructor(private dialog: MatDialog){}
//...
public openDialog(){
    this.dialogRef = this.dialog.open(someDialogComponent, {
      width: '300px'
    });
}

✓ Make a change on dialog or on dialog's parent controller
✓ HMR is triggered (yay)
✖ Dialog hangs in dead state, page is essentially frozen due to the dialog and backdrop being "stuck" and unclickable

I have tried to hook into ngOnInit and ngOnDestroy in the parent or dialog controller to close the dialog reference if one exists, I've also tried to dialog.closeAll(), but this has not worked. Also, ideally the dialog wouldn't have to close, but I can't seem to fix this zombie dialog issue.

Has anyone encountered this?

like image 444
Augie Gardner Avatar asked Dec 07 '17 16:12

Augie Gardner


People also ask

What is angular material dialog?

MatDialog creates modal dialogs that implements the ARIA role="dialog" pattern by default. You can change the dialog's role to alertdialog via MatDialogConfig . You should provide an accessible label to this root dialog element by setting the ariaLabel or ariaLabelledBy properties of MatDialogConfig .

How do I close angular material dialog from TS file?

To close a mat dialog from its component. ts file or programmatically, we have to inject the MatDialogRef<DialogComponentName> in its constructor. The MatDialogRef has a built-in method close() , which you can call anytime whenever you want to close the mat dialog box.

How do you use MatDialogRef?

In the dialog component, we need to create an instance of 'MatDialogRef' which we should import from '@angular/material/dialog'. Import 'MatDialogModule' from '@angular/material' in app. module. ts file.

How do I get data from MatDialog?

You can pass any kind of data to the component by adding data to the MatDialog's open() options. }, }); You can retrieve the data by injecting MAT_DIALOG_DATA in the opened component.


1 Answers

I've been struggling with this and found a less than ideal solution to get by for now. It removes any angular dialogs from the DOM, during the hmr destroy event.

const elements = document.getElementsByClassName('cdk-overlay-container');
for (let i = 0; i < elements.length; i++) {
  elements[i].innerHTML = '';
}

Then in the OnInit, we re-create all the dialogs that were open, passing in their data. The only problem is that this solution retains old dialog instances, so they are not dismiss-able from a background click. However, if the dialog has a close button wired up on it, then it will dismiss properly. The OpenDialogs property in StateService could probably be changed to a TemplateRef[] full of the componentInstances, this might solve the issue, but I'm not sure.

Anyway, a hack solution until official support for dialogs + hmr arrives.

app.module.ts

export class AppModule {
  constructor(private state: StateService, public dialog: MatDialog) { }

  OnInit(store) {
    if (store !== undefined) {
      this.state.SetState(store.State);

      for (let i = 0; i < this.state.OpenDialogs.length; i++) {
        const t = this.state.OpenDialogs[i].componentInstance;
        this.dialog.open(t.constructor, { data: t.data });
      }
    }
  }

  OnDestroy(store) {
    this.state.OpenDialogs = this.dialog.openDialogs;
    store.State = this.state;

    const elements = document.getElementsByClassName('cdk-overlay-container');
    for (let i = 0; i < elements.length; i++) {
      elements[i].innerHTML = '';
    }
  }
}

state.service.ts:

import { Injectable } from '@angular/core';
import { MatDialogRef } from '@angular/material';

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

  public OpenDialogs: MatDialogRef<any>[];

  constructor() {
  }

  public SetState(_state: StateService) {
    this.OpenDialogs = _state.OpenDialogs;
  }
}

main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';


if (environment.production) {
  enableProdMode();
}

// tslint:disable-next-line:no-shadowed-variable
function bootstrap(AppModule) {
  return platformBrowserDynamic().bootstrapModule(AppModule)
    .then(moduleRef => {
      if (environment.hmr) {
        if (module['hot']) {
          module['hot']['accept']();
          if (moduleRef.instance['OnInit']) {
            if (module['hot']['data']) {
              moduleRef.instance['OnInit'](module['hot']['data']);
            }
          }
          if (moduleRef.instance['OnStatus']) {
            module['hot']['apply']((status) => {
              moduleRef.instance['OnStatus'](status);
            });
          }
          if (moduleRef.instance['OnCheck']) {
            module['hot']['check']((err, outdatedModules) => {
              moduleRef.instance['OnCheck'](err, outdatedModules);
            });
          }
          if (moduleRef.instance['OnDecline']) {
            module['hot']['decline']((dependencies) => {
              moduleRef.instance['OnDecline'](dependencies);
            });
          }

          module['hot']['dispose'](store => {
            if (moduleRef.instance['OnDestroy']) {
              moduleRef.instance['OnDestroy'](store);
            }
            moduleRef.destroy();
            if (moduleRef.instance['AfterDestroy']) {
              moduleRef.instance['AfterDestroy'](store);
            }
          });
        }
      }

      return moduleRef;
    });
}

bootstrap(AppModule);
like image 56
seabass Avatar answered Sep 23 '22 04:09

seabass