1 | <p align="center">
|
2 | <a href="https://github.com/PatrickJS/angular-hmr" target="_blank">
|
3 | <img src="https://cloud.githubusercontent.com/assets/1016365/26220655/77e69902-3be1-11e7-8305-87471affe598.png" alt="Angular HMR" width="500" height="320"/>
|
4 | </a>
|
5 | </p>
|
6 |
|
7 |
|
8 | # Angular Hot Module Replacement
|
9 | > Angular-HMR
|
10 | Hot Module Reloading for Webpack and Angular. All versions of Angular and Webpack will work with this module
|
11 |
|
12 | `npm install @angularclass/hmr`
|
13 |
|
14 | ![hmr-state-dom](https://cloud.githubusercontent.com/assets/1016365/18380378/e573320e-762b-11e6-99e0-cc110ffacc6a.gif)
|
15 |
|
16 | `main.browser.ts`
|
17 | ```typescript
|
18 | import { removeNgStyles, createNewHosts, bootloader } from '@angularclass/hmr';
|
19 |
|
20 | @NgModule({
|
21 | bootstrap: [ App ],
|
22 | declarations: [ App ],
|
23 | imports: [
|
24 | // Angular 2
|
25 | BrowserModule,
|
26 | FormsModule,
|
27 | HttpModule,
|
28 | RouterModule.forRoot([], {
|
29 | useHash: true
|
30 | }),
|
31 | // app
|
32 | appModule
|
33 | // vendors
|
34 | ],
|
35 | providers: []
|
36 | })
|
37 | class MainModule {
|
38 | constructor(public appRef: ApplicationRef) {}
|
39 | hmrOnInit(store) {
|
40 | if (!store || !store.state) return;
|
41 | console.log('HMR store', store);
|
42 | console.log('store.state.data:', store.state.data)
|
43 | // inject AppStore here and update it
|
44 | // this.AppStore.update(store.state)
|
45 | if ('restoreInputValues' in store) {
|
46 | store.restoreInputValues();
|
47 | }
|
48 | // change detection
|
49 | this.appRef.tick();
|
50 | delete store.state;
|
51 | delete store.restoreInputValues;
|
52 | }
|
53 | hmrOnDestroy(store) {
|
54 | var cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement);
|
55 | // recreate elements
|
56 | store.disposeOldHosts = createNewHosts(cmpLocation)
|
57 | // inject your AppStore and grab state then set it on store
|
58 | // var appState = this.AppStore.get()
|
59 | store.state = {data: 'yolo'};
|
60 | // store.state = Object.assign({}, appState)
|
61 | // save input values
|
62 | store.restoreInputValues = createInputTransfer();
|
63 | // remove styles
|
64 | removeNgStyles();
|
65 | }
|
66 | hmrAfterDestroy(store) {
|
67 | // display new elements
|
68 | store.disposeOldHosts()
|
69 | delete store.disposeOldHosts;
|
70 | // anything you need done the component is removed
|
71 | }
|
72 | }
|
73 |
|
74 | export function main() {
|
75 | return platformBrowserDynamic().bootstrapModule(MainModule)
|
76 | // use `hmrModule` or the "@angularclass/hmr-loader"
|
77 | .then((ngModuleRef: any) => {
|
78 | // `module` global ref for webpackhmr
|
79 | // Don't run this in Prod
|
80 | return hmrModule(ngModuleRef, module);
|
81 | });
|
82 | }
|
83 |
|
84 | // boot on document ready
|
85 | bootloader(main);
|
86 |
|
87 | ```
|
88 | `bootloader` is only needed to detect that the dom is ready before bootstraping otherwise bootstrap. This is needed because that dom is already ready during reloading.
|
89 |
|
90 | ## Important Helpers
|
91 | * **removeNgStyles**: remove angular styles
|
92 | * **createNewHosts and disposeOldHosts**: recreate root elements for bootstrapping
|
93 | * **bootloader**: boot on document ready or boot if it's already ready
|
94 | * **createInputTransfer** and **restoreInputValues**: transfer input DOM state during replacement
|
95 |
|
96 | ## Production
|
97 | In production you only need bootloader which just does this:
|
98 | ```typescript
|
99 | export function bootloader(main) {
|
100 | if (document.readyState === 'complete') {
|
101 | main()
|
102 | } else {
|
103 | document.addEventListener('DOMContentLoaded', main);
|
104 | }
|
105 | }
|
106 | ```
|
107 | You would bootstrap your app the normal way, in production, after dom is ready. Also, in production, you should remove the loader:
|
108 |
|
109 | ___
|
110 |
|
111 | ## @NGRX/platform (NGRX 4.x.x)
|
112 | To hook into NGRX 4 you simply need to supply a reducer to set the state, and include it in your development metaReducers.
|
113 | ```typescript
|
114 | // make sure you export for AoT
|
115 | export function stateSetter(reducer: ActionReducer<any>): ActionReducer<any> {
|
116 | return function(state: any, action: any) {
|
117 | if (action.type === 'SET_ROOT_STATE') {
|
118 | return action.payload;
|
119 | }
|
120 | return reducer(state, action);
|
121 | };
|
122 | }
|
123 | ```
|
124 | In your root reducer you can do something like this to include it in your `metaReducers`.
|
125 | You should access your environment here and only include this in development.
|
126 | ```typescript
|
127 | /**
|
128 | * By default, @ngrx/store uses combineReducers with the reducer map to compose
|
129 | * the root meta-reducer. To add more meta-reducers, provide an array of meta-reducers
|
130 | * that will be composed to form the root meta-reducer.
|
131 | */
|
132 | export const metaReducers: ActionReducer<any, any>[] = [stateSetter]
|
133 | ```
|
134 | Simply supply the metaReducer to the `StoreModule` and your hmr is hooked in.
|
135 | ```typescript
|
136 | StoreModule.forRoot(reducers, { metaReducers }),
|
137 | ```
|
138 |
|
139 |
|
140 |
|
141 | enjoy — **PatrickJS**
|