Search code examples
angularangular2-services

Resetting Angular 2 App


My Angular 2 app has a logout feature. We want to avoid doing a page reload if we can (i.e. document.location.href = '/';), but the logout process needs to reset the app so when another user logs in there's no residual data from the previous session.

Here's our main.ts file:

import 'es6-shim/es6-shim';
import './polyfills';    
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { ComponentRef, enableProdMode } from '@angular/core';
import { environment } from '@environment';
import { AppModule } from './app/app.module';

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

const init = () => {
  platformBrowserDynamic().bootstrapModule(AppModule)
  .then(() => (<any>window).appBootstrap && (<any>window).appBootstrap())
  .catch(err => console.error(err));
};

init();

platformBrowserDynamic().onDestroy(() => {
  init();
});

You can see that I'm trying to call the init() method when the application is destroyed. The logout method in our user-authentication.service initiates destroy:

logout() {   
  this.destroyAuthToken();  
  this.setLoggedIn(false);
  this.navigateToLogin()
  .then(() => {
    platformBrowserDynamic().destroy();
  });
}

This gives the following error:

The selector "app-root" did not match any elements

Any help appreciated.


Solution

  • I ended up figuring this out in the end. This could be done more simply than my implementation, but I wanted to keep the bootstrapping in main.ts rather than stick it in the service that requests the restart.

    1. Create a singleton that provides a way for Angular and non-Angular (main.ts) to communicate:

    boot-control.ts:

    import { Observable } from 'rxjs/Observable';
    import { Subject } from 'rxjs/Subject';
    export class BootController {
      private static instance: BootController;
      private _reboot: Subject<boolean> = new Subject();
      private reboot$ = this._reboot.asObservable();
    
      static getbootControl() {
        if (!BootController.instance) {
          BootController.instance = new BootController();
        }
        return BootController.instance;
      }
    
      public watchReboot() {
        return this.reboot$;
      }
    
      public restart() {
        this._reboot.next(true);
      }
    }
    
    1. Adjust main.ts to subscribe to the reboot request:

    main.ts:

    import { enableProdMode, NgModuleRef, NgModule } from '@angular/core';
    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { AppModule } from './app/app.module';
    import { environment } from './environments/environment';
    import { BootController } from './boot-control';
    
    if (environment.production) {
      enableProdMode();
    }
    
    const init = () => {
      platformBrowserDynamic().bootstrapModule(AppModule)
      .then(() => (<any>window).appBootstrap && (<any>window).appBootstrap())
      .catch(err => console.error('NG Bootstrap Error =>', err));
    }
    
    // Init on first load
    init();
    
    // Init on reboot request
    const boot = BootController.getbootControl().watchReboot().subscribe(() => init());
    
    1. Add NgZone to the service that triggers the logout:

    user-auth.service.ts:

    import { BootController } from '@app/../boot-control';
    import { Injectable, NgZone } from '@angular/core';
    
    @Injectable()
    export class UserAuthenticationService {
        constructor (
            private ngZone: NgZone,
            private router: Router
        ) {...}
    
        logout() {
            // Removes auth token kept in local storage (not strictly relevant to this demo)
            this.removeAuthToken();
    
            // Triggers the reboot in main.ts        
            this.ngZone.runOutsideAngular(() => BootController.getbootControl().restart());
    
            // Navigate back to login
            this.router.navigate(['login']);
        }
    }
    

    The NgZone requirement is to avoid the error:

    Expected to not be in Angular Zone, but it is!