PatrickJS / angular-hmr

:fire: Angular Hot Module Replacement for Hot Module Reloading
https://patrickjs.com
Apache License 2.0
505 stars 46 forks source link

Cannot read property 'insertBefore' of null #64

Open EvanBurbidge opened 6 years ago

EvanBurbidge commented 6 years ago

Looks like this is occurring in the createNewHosts function call.

function createNewHosts(cmps) {
    var components = cmps.map(function (componentNode) {
        var newNode = document.createElement(componentNode.tagName);
        // display none
        var currentDisplay = newNode.style.display;
        newNode.style.display = 'none';
        var parentNode = componentNode.parentNode;
        parentNode.insertBefore(newNode, componentNode); //Problem hits here

         //Potential fix simple but could avoid this in future. 
         if ( !!componentNode.parentNode ) {
              var parentNode = componentNode.parentNode;
              parentNode.insertBefore(newNode, componentNode);
         }

        return { currentDisplay: currentDisplay, newNode: newNode };
    });
PatrickJS commented 6 years ago

how are you using createNewHosts

EvanBurbidge commented 6 years ago

Apologies for the delay was not at my computer all weekend. Main.browser.ts

declare const ENV:any;

import './polyfills';
import './rxjs.imports';

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { bootloader } from '@angularclass/hmr';
import {decorateModuleRef} from "./env";
import {AppModule} from "./app/app.module";

if (ENV === 'production'){
    enableProdMode();
}

export function main():Promise<any> {
    return platformBrowserDynamic()
        .bootstrapModule(AppModule)
        .then(decorateModuleRef)
        .catch(err => console.log(err))
}

bootloader(main);

App.module.ts

import {NgModule, ApplicationRef} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser'
import { removeNgStyles, createNewHosts, createInputTransfer } from '@angularclass/hmr';
import { Store , StoreModule} from '@ngrx/store';
import {AppComponent} from './app.comp';
import {rootReducer} from './reducers';
import {counterReducer} from "./counter/counter.reducer";
import {CounterComponent} from "./counter/counter.component";

@NgModule({
    declarations: [AppComponent, CounterComponent],
    imports:[
        BrowserModule,
        StoreModule.provideStore(rootReducer)
    ],
    bootstrap:[AppComponent],
    exports:[]
})

export class AppModule {
    constructor(public appRef: ApplicationRef,
                private _store: Store<AppState>) {
    }

    hmrOnInit(store) {
        if (!store || !store.rootState) return;

        // restore state by dispatch a SET_ROOT_STATE action
        if (store.rootState) {
            this._store.dispatch({
                type: 'SET_ROOT_STATE',
                payload: store.rootState
            });
        }

        if ('restoreInputValues' in store) { store.restoreInputValues(); }
        this.appRef.tick();
        Object.keys(store).forEach(prop => delete store[prop]);
    }
    hmrOnDestroy(store) {
        const cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement);
        this._store.take(1).subscribe(s => store.rootState = s);
        store.disposeOldHosts = createNewHosts(cmpLocation);
        store.restoreInputValues = createInputTransfer();
        removeNgStyles();
    }
    hmrAfterDestroy(store) {
        store.disposeOldHosts();
        delete store.disposeOldHosts;
    }
};

export interface AppState {}
Minh-Van commented 6 years ago

I got same problem. Any update for this issue ?

montella1507 commented 6 years ago

Same here.

HMR.ts

import { NgModuleRef, ApplicationRef } from '@angular/core';
import { createNewHosts, hmrModule } from '@angularclass/hmr';

export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
    let ngModule: NgModuleRef<any>;
    module.hot.accept();
    bootstrap().then((mod) => {
        ngModule = mod;
        return hmrModule(mod, module); // this line is missing in Angular 6 HMR story
    });
    module.hot.dispose(() => {
        const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
        const elements = appRef.components.map(c => c.location.nativeElement);
        const makeVisible = createNewHosts(elements);
        // ngModule.destroy(); - "has been already destroyed error - in NG6" 
        makeVisible();
    });
};

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

import { hmrBootstrap } from './hmr';

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

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);

if (environment.hmr) {
  if (module['hot']) {
    hmrBootstrap(module, bootstrap);
  } else {
    console.error('HMR is not enabled for webpack-dev-server!');
    console.log('Are you using the --hmr flag for ng serve?');
  }
} else {
  bootstrap();
}

app.module.ts

import { NgModule, ApplicationRef } from '@angular/core';
import { removeNgStyles, createNewHosts, bootloader, createInputTransfer } from '@angularclass/hmr';
import { AppComponent } from './app.component';
import { SharedModule } from '@shared/shared.module';
import { CoreModule } from '@core/core.module';
import { StoreModule, ActionReducer, Action, MetaReducer, Store } from '@ngrx/store';
import { testReducer } from '@shared/reducers/test.reducer';
import { take } from 'rxjs/operators';

// make sure you export for AoT
export function stateSetter(reducer: ActionReducer<any>): ActionReducer<any> {
  return function (state: any, action: any) {
    if (action.type === 'SET_ROOT_STATE') {
      console.log(action.payload);
      return action.payload;
    }
    return reducer(state, action);
  };
}

export const xyz: MetaReducer<{ count: any }, Action>[] = [stateSetter];

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    CoreModule,
    StoreModule.forRoot({ count: testReducer }, { metaReducers: xyz }),
    SharedModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {

  constructor(public appRef: ApplicationRef, private _store: Store<any>) { }

  hmrOnDestroy(store) {
    const cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement)
    this._store.pipe(take(1)).subscribe(s => store.rootState = s);
    store.disposeOldHosts = createNewHosts(cmpLocation);  // <<<<<<<<====== crash - insertBefore
    store.restoreInputValues = createInputTransfer();
    removeNgStyles();
  }

  hmrAfterDestroy(store) {
    store.disposeOldHosts();
    delete store.disposeOldHosts;
  }

  hmrOnInit(store) {
    if (!store || !store.rootState) {
      return;
    }

    // restore state by dispatch a SET_ROOT_STATE action
    if (store.rootState) {
      this._store.dispatch({
        type: 'SET_ROOT_STATE',
        payload: store.rootState
      });
    }

    if ('restoreInputValues' in store) { store.restoreInputValues(); }
    this.appRef.tick();
    Object.keys(store).forEach(prop => delete store[prop]);
  }
}
montella1507 commented 6 years ago

The problem seems to be here in Helpers.js

var parentNode = componentNode.parentNode; <<< componentNode is "app-component" selector, parentNode is "NULL".. however.. parent node in DOM is
var currentDisplay = newNode.style.display; newNode.style.display = 'none'; parentNode.insertBefore(newNode, componentNode);

maybe it is because of angular 6 ?