9

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?

Edric
  • 24,639
  • 13
  • 81
  • 91
Augie Gardner
  • 2,749
  • 3
  • 25
  • 36
  • 3
    I've encountered this exact issue just today, as I was implementing hmr in my project. It's rather strange. Updating styles while no dialogs are open work flawlessly. But once a dialog is open, any file save that requires recompilation (changing component styles, html or ts) causes the Material Dialog to lose it's binding to the Material Dialog style, which in effect causes it to be stuck on the screen. – Asaf Agranat Dec 21 '17 at 20:20
  • 3
    Found this [reported issue](https://github.com/angular/material2/issues/749) that discusses it – Asaf Agranat Dec 21 '17 at 20:30

1 Answers1

5

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);
seabass
  • 1,030
  • 10
  • 22
  • Anyone find an update, or do we still need a workaround? – Tim Harker Jul 15 '19 at 02:35
  • 1
    The same workaround works for Primeng Dialogs. @TimHarker Corresponding github issue is closed by bot. Obviously dozen men is not big enough crowd. https://github.com/angular/angular-cli/issues/9600 – alehro Oct 22 '20 at 10:52