I have just attempted to add Angular Material design to an application. During the process, I was testing the counter.component.ts page, by changing the increment to 5 (this.currentCount+=5) and pressing Save. Hot module replacement kicked in, and the page reloaded, doubling up the content.
This is a simple example of an Angular 2 component.
Current count: 0
Counter
This is a simple example of an Angular 2 component.
Current count: 4
Just to make sure it wasn't me, I loaded up someone else's sample of adding material to the angular starter package from this site, and I have experienced the same thing. Go to this guys page: codeproject example, download his code, get it running (npm install, add @angular/cdk to the package.json and webpack vendors file, run webpack --config webpack.config.vendors.js, then run the application)
Next, go to the counter page, it should show the material button. Click on it a few times to watch the counter go up. Now, without stopping it, go into the counter.component.ts file and change the counter increment: this.currentCount+=25, then click Save. Behind the scenes, webpack will recompile and hot-module-replace, but when the content comes up, it's doubled up.
Any idea how to get HMR to work properly in this situation, or any workaround?
Just to go a little further. I have switched off prerendering. I have gone back to the initial app starter and incrementally added the material components. It's only when I add in the BrowserAnimationsModule into app.module.ts that it changes from proper module replacement to doubling up.
It made no difference switching to NoopAnimationsModule, but removing both Animations Modules altogether made it behave again...but that kind of defeats the purpose.
Ok, I believe I have a fix. When hot module replacement is occuring, the old root element isn't actually deleted from the parentNode after the app module is destroyed. Putting an extra line in to actually delete that element appears to solve the problem. Could someone please verify that this is the correct approach? (not sure if the removing of the app element needs to be in the promise.then?)
From the boot-client.ts file:
import 'reflect-metadata';
import 'core-js/client/shim';
import 'bootstrap'
import 'zone.js';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module.client';
if (module['hot']) {
module['hot'].accept();
module['hot'].dispose(() => {
// Before restarting the app, we create a new root element and dispose the old one
const oldRootElem = document.querySelector('app');
const newRootElem = document.createElement('app');
oldRootElem.parentNode.insertBefore(newRootElem, oldRootElem);
//Start changes
//modulePromise.then(appModule => appModule.destroy());
modulePromise.then(appModule => {
appModule.destroy();
oldRootElem.parentNode.removeChild(oldRootElem);
});
//End changes
});
} else {
enableProdMode();
}
// Note: @ng-tools/webpack looks for the following expression when performing production
// builds. Don't change how this line looks, otherwise you may break tree-shaking.
const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule);
@tonywr71 FYI I just run in the same issue after upgrading to 4.3.3. Your code change worked like a charm... though can not comment about the approach of the fix
Thanks for the feedback @janisp. My concern is that if the replacement wasn't actually destroying, and therefore garbage collecting, the previous version of the app. I've been using it for some time and not run into any serious memory leak issues myself, so will close this off.
Thank you for sharing, the same problem with me after I added angular material. I spent my whole day searching for an answer in google good thing I found your solution. save me some development time.
I had this same issue after adding Angular Material. This solution worked, but like @janisp, I can't speak to whether this is a solid fix or not. If anyone determines that it is not, please speak up. Thanks for the help @tonywr71!
I have a different variation of this now. Mine now looks like:
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => {
// Before restarting the app, we create a new root element and dispose the old one
const oldRootElem = document.querySelector('app');
const newRootElem = document.createElement('app');
oldRootElem!.parentNode!.insertBefore(newRootElem, oldRootElem);
modulePromise.then(appModule => {
appModule.destroy();
if (oldRootElem !== null)
{
oldRootElem!.parentNode!.removeChild(oldRootElem);
}
});
});
} else {
enableProdMode();
}
to get around some other issues I was experiencing (forget what they were now!)
Most helpful comment
Ok, I believe I have a fix. When hot module replacement is occuring, the old root element isn't actually deleted from the parentNode after the app module is destroyed. Putting an extra line in to actually delete that element appears to solve the problem. Could someone please verify that this is the correct approach? (not sure if the removing of the app element needs to be in the promise.then?)
From the boot-client.ts file: