Components: md-overlay-container duplicated with angular-hmr

Created on 23 Jun 2016  Â·  18Comments  Â·  Source: angular/components

Bug:

When using Hot Module Reload and
provide(OVERLAY_CONTAINER_TOKEN, {useValue: createOverlayContainer()}),
each reload duplicates md-overlay-container overlay div

What is the expected behavior?

Only one overlay div

What is the current behavior?

Overlay div duplicated
image

P5 cdoverlay help wanted

Most helpful comment

Please, HMR is reaaaaaaaaaaaaaaaallly necessary feature for us, EVERY DAY!

Dear Material team, Could you make HMR work properly with material 2?

All 18 comments

@zigzag95 Out of curiousity, which date picker is that? I thought the material date picker hadn't been implemented yet. Looks alot like react's material-ui datepicker.

@zigzag95 thanks

It's happen with me too with MdTooltip when I route from one to another route; the Overlay div duplicated many time any temp solution

I've only switched to using HMR (thank god I found it) and this issue seems to still be present.
When you leave a dialog open and edit your code, the overlay will not go away and you can't interact with your app unless:

  • [ ] You delete the active overlay through the chrome dev tools
  • [ ] Reload your browser

I'm experiencing the same as @borislemke reports. I added HMR to my project and if there's a dialog open, a ghost overlay-container is added.

More importantly, the dialog loses it's bind to the styles. If you look at the <head>section of the document, among the injected <styles> tags you can find the specific <style> dedicated to mat-dialog-container. After a save (with hmr enabled), this style tag disappears. Angular "forgets" to inject the style back to the document.

To reproduce:

  1. Enable HMR on a project with Angular Material 2
  2. Set up a Material Dialog and open it
  3. Inspect the mat-dialog-container element
  4. In the inspector, click on the <style>…</style> link to the right of the mat-dialog-container class name - It'll take you to the section and focus you on the relevant <style> tag
  5. Change anything in your code - a TS, HTML or SCSS of any component, and save
  6. Hot loading occurs without errors
  7. The dialog corrupts as its relevant <style> tag in the head disappears.
  8. More than this is happening, as you can notice that elements in the dialog are not clickable (even when you get to them with the inspector's help)

I hope someone can get to the bottom of this. As I work on dialogs at the moment, I have to disable HMR as it makes it impossible to save any changes without a manual reload of the page.

Before HMR reload
screenshot - 21_12_2017 22_57_42

After HMR reload
screenshot - 21_12_2017 22_58_09

I'm chiming in on this issue as well. I had opened a SO issue here, so far no answers. Subbed to this thread.

Please, HMR is reaaaaaaaaaaaaaaaallly necessary feature for us, EVERY DAY!

Dear Material team, Could you make HMR work properly with material 2?

This is also disrupting my team's workflow at the minute so I'm going to have a crack at creating a PR for this but for anyone else interested here's a repo that reproduces the issue https://github.com/DatacomNelson/MaterialHMR

this is a hack solution to get by for now. It removes any angular dialogs, and your app is repsonsive again. On the hmr destroy event, we nuke any dialogs that exist.

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.

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;
  }
}

and 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);

Any update on this issue, it's been over two years and to be honest while the 'hack solution' suggested by seabass223 works it's quite cumbersome and should not be required.

also facing this problem, its quite annoying me, is there any update?
HMR is a nice feature, I need it!!! but with this problem... I just disabled it.

As mentioned in this isssue: Successful hot module replacement requires the components to be designed. Really hope this problem could be resolved soon.

If anyone wants to contribute a PR for this I'll be happy to take a look

@torabian dude beat it with the completely unrelated self promotion

I was trying to reproduce this with angular 7.2.5, angular cli 7.3.1 on Windows 10 and was not able to reproduce it. On each HMR reload the dialog closes and overlay div disappears from the page. I also tried steps @AsafAgranat suggested and styles did not break. Attached is the sample I have tested this. To test with my sample do the following:

  • inzip
  • npm install
  • npm run start

This will start the application with HMR.

Are there any additional steps needed to reproduce this?

dialog-with-hmr.zip

It might be fixed now if the HMR is smart enough to invoke the ngOnDestroy of the loaded NgModules / providers.

@jelbourn what is the default setting for this to work correctly? Something like this does not work for my if a hot module reload is performed:

export class ExampleComponent implements OnInit, OnDestroy {
  constructor(
    private dialog: MatDialog
  }
  ...
  ngOnDestroy {
    this.dialog.closeAll()
  }
}
Was this page helpful?
0 / 5 - 0 ratings