Angular Material Dialog & Hot Module Reload
Asked Answered
T

1

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?

Tournai answered 7/12, 2017 at 16:56 Comment(2)
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.Bensen
Found this reported issue that discusses itBensen
I
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);
Ir answered 16/5, 2018 at 17:29 Comment(2)
Anyone find an update, or do we still need a workaround?Ilona
The same workaround works for Primeng Dialogs. @TimHarker Corresponding github issue is closed by bot. Obviously dozen men is not big enough crowd. github.com/angular/angular-cli/issues/9600Alisaalisan

© 2022 - 2024 — McMap. All rights reserved.