web-dev-qa-db-fra.com

Le remplacement à chaud du module recharge l'application entière au lieu d'un composant spécifique

J'ai créé un nouveau projet angular et configuré HMR comme expliqué ici: https://github.com/angular/angular-cli/wiki/stories-configure-hmr =

Le projet contient le composant principal (parent) qui a router-outlet et des liens vers 3 composants enfants chargés paresseusement.

Remarque: j'utilise également RouteReuseStrategy personnalisé mais cela n'a aucun effet sur le HMR pour autant que j'ai testé.

Quel que soit le fichier que je modifie - .html ou .ts (parent/enfants), l'application entière se recharge.

J'ai mis en place un dépôt de base qui peut être trouvé ici: https://github.com/ronfogel/demo-hmr

16
Ron

Ce comportement est attendu, je vais essayer d'expliquer ce qui se passe.

Le remplacement de module à chaud que angular angulaire a mis en place est vraiment juste en train de redémarrer toute l'application d'une manière plus générique et avec le support de plusieurs racines d'application, mais si vous mettez les abstractions de côté, il suffit de supprimer app-root tag, en l'ajoutant à nouveau et en amorçant à nouveau AppModule, donc toute l'application change:

export const hmrBootstrap = (
  // webpack stuff
  module: any,
  // bootstrap is AppModule bootstrapper 
  bootstrap: () => Promise<NgModuleRef<any>>
) => {
  let ngModule: NgModuleRef<any>;
  module.hot.accept();
  // bootstraps AppModule ecery time a HMR is needed
  // sets ngModule equal to AppModule if successful (unnecessary)
  bootstrap().then(mod => (ngModule = mod));
  module.hot.dispose(() => {
    // next two lines get native element for all `app-root` tags
    // that exist in `index.html`
    const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
    const elements = appRef.components.map(c => c.location.nativeElement);
    // I will share createNewHosts code below it's nothing fancy just
    // the simple add and delete i mentioned
    const makeVisible = createNewHosts(elements);
    //destroy the current AppModule and finalize deletion
    ngModule.destroy();
    makeVisible();
  });
};
8
Nima Hakimi

C'est ce que j'utilise pour le dernier Angular, qui fonctionne très bien. Vous pouvez l'essayer ...

// main.ts
import { bootloader, createInputTransfer, createNewHosts, removeNgStyles } 
    from '@angularclass/hmr/dist/helpers'; // For correct treeshaking

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

type HmrModule<S> = { appRef: ApplicationRef }
type HmrNgrxModule<S, A> = HmrModule<S> & { 
  store: { dispatch: (A) => any } & Observable<S>,
  actionCreator: (s: S) => A
}

const isNgrxModule = <S, A, M extends HmrNgrxModule<S, A>>
  (instance: HmrModule<S> | HmrNgrxModule<S, A>): instance is M =>
    !!((<M>instance).store && (<M>instance).actionCreator);

function processModule<S, A, M extends HmrModule<S> | HmrNgrxModule<S, A>>(ngModuleRef: NgModuleRef<M>) {

  const hot = module['hot'];
  if (hot) {

    hot['accept']();

    const instance = ngModuleRef.instance;
    const hmrStore = hot['data'];

    if (hmrStore) {
      hmrStore.rootState 
        && isNgrxModule(instance) 
        && instance.store.dispatch(instance.actionCreator(hmrStore.rootState));
      hmrStore.restoreInputValues && hmrStore.restoreInputValues();
      instance.appRef.tick();
      Object.keys(hmrStore).forEach(prop => delete hmrStore[prop]);
    }

    hot['dispose'](hmrStore => {
      isNgrxModule(instance) && instance.store.pipe(take(1)).subscribe(s => hmrStore.rootState = s);
      const cmpLocation = instance.appRef.components.map(cmp => cmp.location.nativeElement);
      const disposeOldHosts = createNewHosts(cmpLocation);
      hmrStore.restoreInputValues = createInputTransfer();
      removeNgStyles();
      ngModuleRef.destroy();
      disposeOldHosts();
    });
  }
  else {
    console.error('HMR is not enabled for webpack-dev-server!');
    console.log('Are you using the --hmr flag for ng serve?');
  }

  return ngModuleRef;
}

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);
const hmrBootstrap = () => bootloader(() => bootstrap().then(processModule));

environment.hmr
  ? hmrBootstrap()
  : bootstrap();
// app.module.ts
@NgModule({ ... })
export class AppModule {
  constructor(public appRef: ApplicationRef) { ... }
}

La configuration HMR fonctionnera également avec le magasin Ngrx, si vous êtes dans ce genre de choses. Vous pouvez cependant omettre le code de gestion Ngrx.

Espérons que cela aide un peu :-)

6
Heehaaw