web-dev-qa-db-ja.com

Angular)を使用してHMR中に状態を保持する方法

Angularでは、モジュールがホットリロードされた後にアプリケーションの状態を保持する方法はありますか? VueJSで起こることと同様:

VueJS HMR Example

これまでのところ、いくつかのチュートリアルに従ってHMRを動作させることができましたが、実際にページを更新せずにアプリをリロードするだけです。はい、全負荷を速くします。しかし、それがどこにあるのかはまだわかりません。

誰かがこれを実際に機能させることができましたか?

PS:関連しています https://github.com/beeman/tutorial-angular-cli-hmr/issues/4

13
Shane

上記のシーバスのアプローチを試しましたが、うまく機能させるのに少し苦労しました。しかし、私はそれが非常に有用で情報を提供することを発見しました。彼のアイデアを使用して、新しいAngular 6アプリケーションを作成し、HMRビルドを通じてアプリケーションの状態を維持することができました。Githubでプロジェクトを作成したので、他の人が実験したい場合にプロジェクトをプルダウンできます。コードコメントを読み、コンソールログをチェックして、物事が発生する順序とその動作について理解してください。

https://github.com/ermcgrat/NgStarter

プロジェクトのクローンを作成し、npmインストールを実行してから、「npm runstart」でアプリケーションを実行します。 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。これで特に注意が必要なのは、基本的にアプリケーションの状態を2つの形式で維持していることです。これらの1つは、同期アクセス用のプレーンオールドバニラオブジェクトです(モジュールdisposeの非同期関数は、新しいモジュールが評価される前に終了することが保証されないため、これはHMRの再構築に必要です)。 2つ目は、アプリケーションの状態の監視可能なバージョンであるため、さまざまなコンポーネントが状態の変更/更新を簡単に監視できます。

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のような状態ライブラリはこのようなものと完全に統合されると私は考えています。

8
emcgrath

素晴らしい質問です。私はついにwebpackhmrを動作させ、angular状態を維持しました。多くの頭痛の種がありました。私の最終的な実装では、無効にする手段として新しい environment.hmr.ts を使用しています。本番環境またはテスト開発サーバーのhmr。提案された.hmrファイルを実装せず、代わりにそのコードをmain.tsに残しました。

これを考慮してください:あなたはファイルに変更を加えて保存します。 Webpackがコンパイルされます 魔法が起こります 、あなたのAngularアプリは取り壊され、新しい変更と交換されようとしています。

Webpackは_module['hot']['dispose']_で関数を呼び出し、次に前のアプリのAppModuleクラスでOnDestroyを呼び出して、状態を保存する機会を与えます。

次に、Webpackは新しいアプリをロードしてブートストラップし、AppModuleでOnInitを呼び出して、状態である_module['hot']['data']_を渡します。

これらの「ホット」な意味についてもっと読むことができます


main.ts

 
 import {enableProdMode} from '@ angular/core'; 
 import {platformBrowserDynamic} from '@ angular/platform-b​​rowser-dynamic'; 
 
 import {AppModule} from './ app/app.module'; 
 import {environment} from './ environments/environment'; 
 
 if(environment。プロダクション){
 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']){
 // app.module.ts 
 MODULE_REF.instance ['OnInit'](module ['hot'] ['でOnInitを呼び出しますデータ ']); 
} 
} [.__ __。] if(MODULE_REF.instance ['OnStatus']){
 module ['hot'] ['apply']((status)=> {
 MODULE_REF.instance ['OnStatus' ](status); 
}); 
} 
 if(MODULE_REF.instance ['OnCheck']){
 module ['hot'] ['check ']((err、periodedModules)=> {
 MODULE_REF.instance [' OnCheck '](err、periodedModules); 
}); 
} 
 if (MODULE_REF.instance ['OnDecline']){
 module ['hot'] ['decline']((dependencies)=> {
 MODULE_REF.instance ['OnDecline'](dependencies) ; 
}); 
} 
 
 module ['hot'] ['dispose'](store => {
 if(MODULE_REF。 instance ['OnDestroy']){
 // app.module.ts 
 MODULE_REF.instance ['OnDestroy'](store)でOnDestroyを呼び出します; 
} 
 MODULE_REF.destroy(); 
 if(MODULE_REF.instance ['AfterDestroy']){
 MODULE_REF.instance ['AfterDestroy'](store ); 
} 
}); 
} 
} 
 
 return MODULE_REF; 
}); 
} 
 
 bootstrap(AppModule); 
 

ここでは、storeパラメーターとして渡される_module['hot']['data']_に前のアプリの状態を保存できます。これと同じstoreパラメーターが新しいアプリのOnInit(store)に渡され、新しいアプリ内の状態オブジェクトを維持できるようになります。

app.module.ts

 
 export class AppModule {
コンストラクター(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.Sid​​eNavIsOpen = new BehaviorSubject(false); 
 
} 
 
 public SetState(_state:StateService){
 this.Sid​​eNavIsOpen = _state.SideNavIsOpen; 
} 
} 
 
6
seabass