如何使用Angular在HMR期间保持状态

Sha*_*ane 11 webpack webpack-hmr angular

在Angular中,有一种方法可以在模块热重新加载后保留应用程序状态吗?与VueJS中发生的情况类似:

VueJS HMR示例

到目前为止,我已经让HMR在几个教程之后工作了,但它所做的只是重新加载应用程序而不进行实际的页面刷新.快满了,是的.但仍然不是它可能的地方.

有没有人得到这个实际工作?

PS:它与https://github.com/beeman/tutorial-angular-cli-hmr/issues/4有关

小智 8

我尝试过上面的seabass方法,并且在使用它时遇到了一些困难.我确实发现它非常有用和信息.使用他的想法,我能够创建一个新的Angular 6应用程序,并通过HMR构建获得应用程序状态.我在Github上创建了一个项目,所以如果他们想要试验它,其他人可以把它拉下来,因为这是最好的学习方法.阅读代码注释并检查控制台日志,以了解事情发生的顺序以及它们的工作方式.

https://github.com/ermcgrat/NgStarter

克隆项目,执行npm安装,然后使用" npm run start " 运行应用程序.尝试更改AppComponent中的代码并查看hmr的工作原理.

简而言之,我能够通过创建状态服务来实现状态持久性,并在我的AppModulehmrBootstrap中利用它.首先,我开始使用Angular CLI团队指定的基本HMR功能:

https://github.com/angular/angular-cli/wiki/stories-configure-hmr

这将使HMR工作,但它不会持续状态.我扩展了hmr.ts文件,以便在处理(卸载)模块时保存我们的状态.评估新模块时,它将从HMR模块读取此保存状态并将其注入我们的新模块:

hmr.ts

export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
  module.hot.accept();

  bootstrap().then(mod => {

    // Attach a dispose handler. When this module is replaced, we will first run this code before
    // evaluating the new module. (eg. running main.ts)
    module.hot.dispose(data => {

      if (mod.instance.hmrOnDestroy) {
        mod.instance.hmrOnDestroy(data);
      }

      const appRef: ApplicationRef = mod.injector.get(ApplicationRef);
      const elements = appRef.components.map(c => c.location.nativeElement);
      const makeVisible = createNewHosts(elements);
      mod.destroy();
      makeVisible();
    });

    // Does this module have an hmrOnInit method for us to run?
    // And is there state data from previous unloaded module to initalize?
    let prevData;
    if (module.hot.data && module.hot.data.appState) {
      prevData = module.hot.data.appState;
    }
    if (mod.instance.hmrOnInit && prevData) {
      mod.instance.hmrOnInit(prevData);
    }

  });
};
Run Code Online (Sandbox Code Playgroud)

这是我们的AppModule,实现上面使用的hmrOnInithmrOnDestroy方法.值得注意的是hmrOnInit如何通过状态服务恢复应用程序状态(如果存在).

app.module.ts

export class AppModule {

  constructor(private appRef: ApplicationRef, private stateService: AppStateService) { }

  hmrOnInit(prevState: any) {
    if (prevState) {
      this.stateService.saveAppState(prevState);
      // change detection.
      this.appRef.tick();
    }
  }

  hmrOnDestroy(data: any) {
    // Here we will increment our hmrBuilds counter, and then save our state to
    // data (module.hot.data), so that it will be available to the new module.
    const hmrBuilds = this.stateService.getHmrBuilds() + 1;
    this.stateService.saveHmrBuilds(hmrBuilds);
    data.appState = this.stateService.getAppState();
  }
}
Run Code Online (Sandbox Code Playgroud)

最后是AppStateService.唯一特别棘手的是我们基本上以两种形式维护应用程序状态.其中一个是用于同步访问的普通旧香草对象(这是HMR重建所必需的,因为在评估新模块之前,无法保证模块配置中的异步功能完成).第二个是我们的应用程序状态的可观察版本,因此各种组件可以轻松地观察状态的更改/更新.

app.state.service.ts

export class AppStateService {

  // attach various component states to this object
  // We maintain an object for synchronous use by the HMR, and an Observable for use by the application and its templates.
  private appState: IAppState = { hmrBuilds: 0 };
  private appStateSubject = new BehaviorSubject<IAppState>({ hmrBuilds: 0 });
  public appState$: Observable<IAppState> = this.appStateSubject.asObservable();

  constructor() { }

  public getAppState() {
    return this.appState;
  }

  public getHmrBuilds(): number {
    return this.appState.hmrBuilds ? this.appState.hmrBuilds : 0;
  }

  public saveAppState(newState: IAppState) {
    this.appState = newState;
    this.appStateSubject.next(newState);
  }

  public saveHmrBuilds(buildNum: number) {
    this.appState.hmrBuilds = buildNum;
  }

}
Run Code Online (Sandbox Code Playgroud)

最后,任何应用程序组件现在都可以观察到这个appState $并在其组件代码或模板中使用它.

我还要指出,这种在HMR构建之间维持状态的方法实质上导致了单一的事实来源.在我看来,像ngrx这样的状态库可以完美地集成到这样的东西中.


sea*_*ass 6

好问题.经过很多头痛之后,我终于让webpack hmr工作并保持角度状态.我的最终实现使用新的environment.hmr.ts作为在生产中或在测试开发服务器上禁用hmr的方法.我没有实现建议的.hmr文件,而是将该代码留在main.ts中

考虑一下:您在文件中进行更改并保存.Webpack编译,魔术发生,你的Angular应用程序即将被拆除并与新的更改交换.

Webpack调用我们的函数,module['hot']['dispose']然后在OnDestroy之前的应用程序的AppModule类中调用它,让我们有机会保存我们的状态.

然后Webpack加载我们的新应用程序,它启动,并调用OnInitAppModule,传递它module['hot']['data']是我们的状态.

您可以阅读更多关于这些"热门"事物的含义


main.ts


    import { enableProdMode } from '@angular/core';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

    import { AppModule } from './app/app.module';
    import { environment } from './environments/environment';

    if (environment.production) {
      enableProdMode();
    }


    function bootstrap(AppModule) {
      return platformBrowserDynamic().bootstrapModule(AppModule)
        .then(MODULE_REF => {

          if (environment.hmr) {
            if (module['hot']) {

              module['hot']['accept']();

              if (MODULE_REF.instance['OnInit']) {
                if (module['hot']['data']) {
                  // Calls OnInit on app.module.ts
                  MODULE_REF.instance['OnInit'](module['hot']['data']);
                }
              }
              if (MODULE_REF.instance['OnStatus']) {
                module['hot']['apply']((status) => {
                  MODULE_REF.instance['OnStatus'](status);
                });
              }
              if (MODULE_REF.instance['OnCheck']) {
                module['hot']['check']((err, outdatedModules) => {
                  MODULE_REF.instance['OnCheck'](err, outdatedModules);
                });
              }
              if (MODULE_REF.instance['OnDecline']) {
                module['hot']['decline']((dependencies) => {
                  MODULE_REF.instance['OnDecline'](dependencies);
                });
              }

              module['hot']['dispose'](store => {
                if (MODULE_REF.instance['OnDestroy']) {
                  // Calls OnDestroy on app.module.ts
                  MODULE_REF.instance['OnDestroy'](store);
                }
                MODULE_REF.destroy();
                if (MODULE_REF.instance['AfterDestroy']) {
                  MODULE_REF.instance['AfterDestroy'](store);
                }
              });
            }
          }

          return MODULE_REF;
        });
    }

    bootstrap(AppModule);


在这里,我们可以将以前应用程序的状态存储module['hot']['data']在作为store参数传入的状态中.这个相同的商店参数被传递到新的应用程序中,OnInit(store)使我们能够维护新应用程序中的任何状态对象.

app.module.ts


     export class AppModule {
          constructor(private _state: StateService) { }

          OnInit(store) {
            if (store !== undefined) {
              this._state.SetState(store.State);
            }
          }

          OnDestroy(store) {
            store.State = this._state;
          }
        }


这是我的基本国家服务.您可能更喜欢在这里使用ngrx进行状态管理,但我觉得这对我的项目来说太过分了.

state.service.ts


    import { Injectable } from '@angular/core';
    import { BehaviorSubject } from 'rxjs/BehaviorSubject';

    @Injectable()
    export class StateService {

      public SideNavIsOpen: BehaviorSubject;

      constructor() {
        this.SideNavIsOpen = new BehaviorSubject(false);

      }

      public SetState(_state: StateService) {
        this.SideNavIsOpen = _state.SideNavIsOpen;
      }
    }