如何使用Angular

时间:2018-04-01 07:30:36

标签: angular webpack webpack-hmr

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

VueJS HMR Example

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

有没有人让它真正起作用?

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

有关

2 个答案:

答案 0 :(得分:7)

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

https://github.com/ermcgrat/NgStarter

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

简而言之,我能够通过创建状态服务来实现状态持久性,并在我的 AppModule hmrBootstrap 中利用它。首先,我开始使用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);
    }

  });
};

这是我们的AppModule,实现上面使用的 hmrOnInit hmrOnDestroy 方法。值得注意的是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();
  }
}

最后是 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;
  }

}

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

我还想指出,这种在HMR构建之间维持状态的方法实质上导致单一的事实来源。在我看来,像 ngrx 这样的州立图书馆可以完美地与这样的东西整合。

答案 1 :(得分:6)

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

请考虑这一点:您在文件中进行更改并保存。 Webpack编译,magic happens,您的Angular应用程序即将拆除并与新更改交换。

Webpack在def interpolate(R_V, rawpoints, Elist, j): pointslist = [] if R_V == 4.0: for i in range(len(rawpoints)): newlst = re.split('(?!\S)\s(?=\S)|(?!\S)\s+(?=\S)', rawpoints[i]) pointslist.append(newlst) pointslist = pointslist[3:] lambdalist = [float(item[0]) for item in pointslist] k_abslist = [float(item[4]) for item in pointslist] xvallist = [(c*h)/(lamb*1e-6) for lamb in lambdalist] k_interp = scipy.interpolate.interp1d(xvallist, k_abslist) return k_interp(Elist[j]) 上调用我们的函数,后者又调用前一个应用程序的AppModule类中的module['hot']['dispose'],让我们有机会保存我们的状态。

然后Webpack加载我们的新应用程序,它引导并在AppModule上调用OnDestroy,并传递它是我们状态的OnInit

You can read more about what these 'hot' things mean

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']中。同样的商店参数会传递到新应用的module['hot']['data']中,从而使我们能够在新应用中维护任何状态对象。

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;
      }
    }