Angular 2 Hot Loading with @ngrx/store and Webpack

Datetime:2016-08-23 00:24:49          Topic: AngularJS  Webpack           Share

I'vebeen waiting for a hot reloading workflow for Angular 2 to emerge. I was happy to discover that one now exists. This is potentially a very big deal for productivity.

There are four pieces to this puzzle, and all of them are now in place:

  • @ngrx/store provides Redux style centralized state management on top of RxJS.
  • Webpack's dev server has built-in hot module replacement .
  • PatrickJS has written an angular2-hmr library that integrates the Angular 2 bootstrap process to Webpack HMR.
  • Mike Ryan has written an @ngrx/store HMR library that integrates angular2-hmr with @ngrx/store .

For state management you can also use Redux instead of @ngrx/store . You'll need to wire it up with angular2-hmr yourself, so that the store state persists through reloads. I haven't tried this but it probably wouldn't be very hard to do.

What's The Benefit?

Hot loading can make an enormous difference in programmer productivity . The basic idea is that you can work on your Angular 2 application and make changes to it while it is running . You don't have to reload the page, and more importantly, you don't even have to restart the app. The code is hot-swapped on the fly.

Here's an example: I'm adding a "remove" feature to this simple list app while it's running. The application state is retained even when the code changes.

Imagine you're working on an application where it takes several steps to reach a certain part of the UI: You need to navigate around, fill in text fields, and so on, until you reach that crucial form validation control you're currently working on.

You may easily need to do tens of iteration cycles to get everything right: Tweak the CSS here, restructure the HTML there, change the validation rules elsewhere. The problem is that whenever you make any change , the application restarts. The reloading itself might be lightning fast, but after that you still have to repeat all those manual navigation steps before you reach the relevant UI state again. This becomes very tedious very quickly.

This is the problem solved by hot loading. You make a change in TypeScript, HTML, or CSS, and it's applied immediately . You stay in the location you were in. Your previous steps are not erased. You can just see the effects of the change and move on to the next one.

Watch Dan Abramov's original Redux talk for more on the benefits of this workflow. He also talks about "time travel", which is a separate but related concept. It is also very much achievable on the Angular 2 stack.

How Do I Set This Up?

One thing to understand about this workflow is that you can't apply it to just any application. You have to manage your state very carefully, so that it is separated from the rest of the app .

This is where either @ngrx/store or Redux can help. Both provide centralized data stores that hold on to your state, as well as the means to make changes to that state in a very simple and well-defined way. (See myRedux tutorial for an extensive example of this.) And that state can then be kept even when the rest of the application is swapped from under it.

Once you're committed to this type of architecture, the rest is just a matter of installing and integrating the libraries mentioned above. Here's how I did it this weekend to get this going with @ngrx/store.

1. Set up an Angular 2 Webpack project

Just follow the Angular 2 Webpack guide on angular.io to get a project going.

2. Enable Webpack's hot module replacement

To enable the Webpack dev server's hot module replacement , add the --hot flag to the start script in package.json .

"start": "webpack-dev-server --inline --hot --progress --port 8080",

3. Install and Integrate angular2-hmr and ngrx-store-hmr

You need two more libraries in your project:

  • The angular2-hmr library integrates Angular 2's bootstrap with Webpack's hot module replacement.
  • The ngrx-store-hmr library then makes it all work with @ngrx/store.

First install angular2-hmr :

npm install angular-hmr -s

Then install ngrx-store-hmr . Here's where things are a bit under construction right now. The library's latest NPM release doesn't support the latest version of @ngrx/store, but there's an as of yet unmerged pull request by Jeremy Fensch that fixes this. I just installed his fork from Github, but I imagine you'll be able to install an actual release very soon.

npm install https://github.com/jFensch/ngrx-store-hmr.git -s

Now we can set things up in src/main.ts . First we need to wrap our Angular bootstrap call into a reusable function that optionally takes the previous application state :

function main(initialState?: any) {
  return bootstrap(AppComponent, [
    provideStore({counter: counterReducer}, initialState)
  ]);
}

This is your regular Angular 2 bootstrap with one crucial difference: As we initialize @ngrx/store, we pass in a second argument for the initial state of the store. When we're not doing a hot reload, it's going to be undefined . But when we are doing a hot reload, it'll contain the previous application state.

Once we have the main function, we'll call it in one of two ways:

  1. If we're in development mode, we'll pull in the ngrx-store-hmr library and let it do the "hot" bootstrap for us by passing it our main function.
  2. If we're not in development mode (i.e. running a production build), we'll just call main ourselves when the page initializes. Hot loading will not be enabled.
if (process.env.ENV !== 'production') {
  let ngrxHmr = require('ngrx-store-hmr/lib/index').hotModuleReplacement;
  ngrxHmr(main, module);
} else {
  document.addEventListener('DOMContentLoaded', () => main());
}

With this setup, HMR should just work. When you load the app and look at the developer console, you should see Webpack saying Hot Module Replacement is enabled. When you make changes to code, you should see them applied more or less immediately, also with some information logged:

Limitations

The main limitation I see with this approach right now is that it only retains state that you actually keep in the @ngrx/store. Component-local state is not retained, so anything you keep in component object properties will vanish on reload.

This is because the whole Angular app is still actually reloaded and re-bootstrapped. The @ngrx/store state is rehydrated but components are not. The latter would require something similar to react-hot-loader , which would generate component proxies at runtime so that the original components can be hotswapped. As far as I know, this doesn't exist in Angular 2 right now, though Minko Gechev did some experimentation a while ago.

What this means is that you get the most out of this workflow if you make your components as stateless as possible and dispatch all meaningful state changes to @ngrx/store, including not just "model state" but also "view state". This may or may not be a good idea anyway. It's an architectural question much discussed in the React community, see e.g. here and here .





About List