Angular 4 Hot Module Reload Explained
During the last two weeks I was doing research on the possibility of implementing Hot Module Reload in Angular 4.
What is Hot Module Reload you can ask?
Hot Module Reload is the ability of the application to update itself when the source code is changed — without full page reload ! HMR is extremely popular in the React world (React makes it easier to implement it), but it’s certainly possible with Angular too. Explaining HMR in React is out of scope of this article.
Getting back to my research.. Almost all the articles were pointing to the @angularclass/hmr which has a fancy flame icon, but that’s where its merits come to an end. I think you already guessed that it wasn’t really working for me and I had to pursue other options. The part that I didn’t really expect was that writing the code myself will be a lot easier than using an external library. In this article I will try to explain in details how HMR works and how we can handle it in Angular 4.
First of all if you are writing an Angular application then most likely you are also using Webpack for compiling it. Webpack has built in support for HMR and that eases the pain a lot. You just need to enable HMR in the Webpack configuration file and server side is covered. Here I can point you to articles in the Webpack documentation that explain the matters in a more than sufficient detail, but please save it for later.
- Hot Module Replacement
- Hot Module Replacement Guide
- Hot Module Replacement API (advanced topic, better consult after reading this article)
Right now all you need to know is at a very high level how it works. The algorithm is as follows:
- You are developing your application & running webpack with webpack-dev-server (or webpack-dev-middleware for custom Node.js applications)
- You make changes in one of your components & save them.
- Webpack discovers that files changed and does an incremental compile.
- Webpack HMR plugin sends the changes to the browser where they are picked up (that’s not 100% accurate, but you get the idea)
- The code that picks those changes is automatically generated by webpack and included in your application. This is where the heavy lifting happens.
- Now comes the part that you need to code yourself — updating the application with the changes. (don’t be afraid, it doesn’t hurt!)
Let’s start the explanations here with the basics. The very first thing that you need to know (or you already do..) is what a module is. Again Webpack documentation has a great article about this. However for the sake of this article you can treat “module” as a synonym for a file (.ts, .js, .html, .scss etc.)
Modules have dependencies. This is something that you might have suspected, but it might not have been obvious. Every Harmony import statement or CommonJS require is declaring a dependency of a module!
Let’s look at a common chain of dependencies for an Angular application:
app.module.ts => app.component.ts => home.component.ts => home.component.html
Now if you change HTML in your home.component.html file you need to either reload the home.component.html or reload the whole home.component or reload the whole app.component or (I think you already figured..) reload the whole app.module.ts ! This is exactly what we will be doing in this article.
This might come as a surprise to you, but this is the easiest way to implement reloading the changes in an Angular application. Please let me know if I’m wrong, but so far no one has invented anything more sophisticated than that in the Angular world. This is where you can surprise me & the world and improve the approach!
The code that does the update is pretty straightforward. It needs to do two things:
- Remove & teardown the current application
- Bootstrap new application
The first is achieved by this function:
The second is achieved by this function:
This one requires a little more explanation. First we are creating an ordinary new application node. Do you remember that you already had to put one like this in your index.html file? It usually looks like this:
We are just inserting an empty node.. Nothing fancy.
Then we are obtaining a reference to an updated app.module.ts file. This time we are using require method instead of harmony imports, because we want to do it in runtime.
The last line is also nothing fancy — bootstrap the module using platformBrowserDynamic — Angular platform that includes the JIT compiler (further explanation outside of the scope of this article).
Now those functions need to be called somewhere. This where the HMR API that I mentioned before in the server side comes to be handy. Please take a look at the complete solution:
- Removing the old application node triggers application tear down process in Angular version 4. I’m not sure how it works and if it is always the case. All the style nodes (with css) were automatically removed by this process, so I didn’t have to do anything additional. I’m relying on this behaviour without deeper understanding of the process.
- Reversing the order (first add new node, then remove old one) ends with an error.
- It happened to me that the Angular teardown process fired after re-creating the new node and in the end the new node disappeared! If this happens to you — wrap the tryBootstrapNewApplication call in setTimeout like this:
4. If you have any comments on this article or solution I’m more than happy to get to know them!
5. At the beginning of the article I mentioned that I had issues with @angularclass/hmr. Here they are:
- It requires webpack loader that hides the HMR API behind arbitrary method names that they picked. It’s a totally unnecessary step.
- It didn’t work for me since I did not clone their whole example and had a little different setup & code.
- They have very poor documentation of the process (hence I decided to write this article!)
- The code is written in a very generic way — this is not necessarily bad, but I believe is an overkill for what it’s worth. (i.e. they pretend to handle the case with multiple root nodes, but it’s not 100% handled correctly..).
- The code requires ApplicationRef to be injected into your module. Again unnecessary.
- There’s more unnecessary code in there.
Stay tuned for part two !
Surprise, surprise — I did not cover the whole topic in this article. The main missing thing is how to persist application state between updates. This is very straightforward for all the Redux implementers (including NGRX platform) since they have global state, but not so much in the classic Angular services approach.
Thanks for reading!