I created a new angular project and set up HMR as explained here: https://github.com/angular/angular-cli/wiki/stories-configure-hmr
The project contains main component (parent) which has router-outlet
and links to 3 child components which are lazy loaded.
Note: I am also using custom RouteReuseStrategy
but it has no effect on the HMR as far as I tested.
No matter what file I am changing - .html or .ts (parent/children) the whole app reloads.
I've set up a basic repo which can be found here: https://github.com/ronfogel/demo-hmr
Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running, without a full reload. This can significantly speed up development in a few ways: Retain application state which is lost during a full reload. Save valuable development time by only updating what's changed.
You can use the CLI to modify the webpack-dev-server configuration with the following command: webpack serve --hot-only . Now let's update the index. js file so that when a change inside print. js is detected we tell webpack to accept the updated module.
HRM is Webpack feature which is also used by Angular11 team since Angular build system is Webpack based. HRM feature enables development faster because it updates limited resource after you save code in project. To enable HMR using Angular 11 cli, following command need to be execute.
Developing a JavaScript application involves reloading the browser each time you save code changes in order to refresh the user interface. Developer tools like Webpack can even run in watch mode to monitor your project files for changes.
This behavior is expected, I will try to explain what is happening.
Hot module replacement that angular has set up is really just re-bootstraping the whole app in a more generic way and with support for multiple app roots, but if you put the abstractions aside its simply deleting app-root
tag, adding it again and bootstraping AppModule
again , so the whole app changes:
export const hmrBootstrap = (
// webpack stuff
module: any,
// bootstrap is AppModule bootstrapper
bootstrap: () => Promise<NgModuleRef<any>>
) => {
let ngModule: NgModuleRef<any>;
module.hot.accept();
// bootstraps AppModule ecery time a HMR is needed
// sets ngModule equal to AppModule if successful (unnecessary)
bootstrap().then(mod => (ngModule = mod));
module.hot.dispose(() => {
// next two lines get native element for all `app-root` tags
// that exist in `index.html`
const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
const elements = appRef.components.map(c => c.location.nativeElement);
// I will share createNewHosts code below it's nothing fancy just
// the simple add and delete i mentioned
const makeVisible = createNewHosts(elements);
//destroy the current AppModule and finalize deletion
ngModule.destroy();
makeVisible();
});
};
This is what I use for the latest Angular, working just fine. You can give it a try...
// main.ts
import { bootloader, createInputTransfer, createNewHosts, removeNgStyles }
from '@angularclass/hmr/dist/helpers'; // For correct treeshaking
if (environment.production) {
enableProdMode();
}
type HmrModule<S> = { appRef: ApplicationRef }
type HmrNgrxModule<S, A> = HmrModule<S> & {
store: { dispatch: (A) => any } & Observable<S>,
actionCreator: (s: S) => A
}
const isNgrxModule = <S, A, M extends HmrNgrxModule<S, A>>
(instance: HmrModule<S> | HmrNgrxModule<S, A>): instance is M =>
!!((<M>instance).store && (<M>instance).actionCreator);
function processModule<S, A, M extends HmrModule<S> | HmrNgrxModule<S, A>>(ngModuleRef: NgModuleRef<M>) {
const hot = module['hot'];
if (hot) {
hot['accept']();
const instance = ngModuleRef.instance;
const hmrStore = hot['data'];
if (hmrStore) {
hmrStore.rootState
&& isNgrxModule(instance)
&& instance.store.dispatch(instance.actionCreator(hmrStore.rootState));
hmrStore.restoreInputValues && hmrStore.restoreInputValues();
instance.appRef.tick();
Object.keys(hmrStore).forEach(prop => delete hmrStore[prop]);
}
hot['dispose'](hmrStore => {
isNgrxModule(instance) && instance.store.pipe(take(1)).subscribe(s => hmrStore.rootState = s);
const cmpLocation = instance.appRef.components.map(cmp => cmp.location.nativeElement);
const disposeOldHosts = createNewHosts(cmpLocation);
hmrStore.restoreInputValues = createInputTransfer();
removeNgStyles();
ngModuleRef.destroy();
disposeOldHosts();
});
}
else {
console.error('HMR is not enabled for webpack-dev-server!');
console.log('Are you using the --hmr flag for ng serve?');
}
return ngModuleRef;
}
const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);
const hmrBootstrap = () => bootloader(() => bootstrap().then(processModule));
environment.hmr
? hmrBootstrap()
: bootstrap();
// app.module.ts
@NgModule({ ... })
export class AppModule {
constructor(public appRef: ApplicationRef) { ... }
}
The HMR
setup will work with Ngrx store as well, if you are into this sort of stuff. You can omit the Ngrx-handling code, though.
Hope this helps a little :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With