import { Component } from '@angular/core';
import { Store } from '@ngrx/store';

import { AppState } from './app.state';
import { IUser } from './user.interface';

@Component({
  selector: 'my-app',
  template: `
    <user [user]="user | async"></user>
    
    <hr>
    
    <login></login>
    
    <!-- for debug purpose only -->
    <ngrx-store-log-monitor toggleCommand="ctrl-h" positionCommand="ctrl-m"></ngrx-store-log-monitor>
  `
})
export class AppComponent {
  public user: Observable<IUser>;
  
  constructor(public store: Store<AppState>){
      this.user = store.select('user');
  }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';

import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { StoreLogMonitorModule, useLogMonitor } from '@ngrx/store-log-monitor';

import { AppComponent } from './app.component';
import { UserComponent } from './user.component';
import { LoginComponent } from './login.component';
import { UserReducer } from './user.reducer';
import { UserService } from './user.service';

@NgModule({
  imports: [
    BrowserModule,
    CommonModule,
    FormsModule,
    StoreModule.provideStore({
      user: UserReducer
    }),
    
    // for debug purpose only
    StoreDevtoolsModule.instrumentStore({
      maxAge: 50,
      monitor: useLogMonitor({
        visible: true,
        position: 'right'
      })
    }),
    StoreLogMonitorModule
  ],
  declarations: [
    AppComponent,
    UserComponent,
    LoginComponent
  ],
  providers: [
    UserService
  ],
  bootstrap: [ AppComponent ]
})

export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
<!DOCTYPE html>
<html>
  <head>
    <title>Angular QuickStart</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link data-require="bootstrap@3.3.6" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
    
    <script src="https://unpkg.com/core-js/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js@0.6.25?main=browser"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
    <script src="https://unpkg.com/systemjs@0.19.27/dist/system.src.js"></script>
    
    <script src="systemjs.config.js"></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>
</html>
# Ngrx Store and Effects

This is a basic Plunkr with ngrx/store and ngrx/effects already setup
/**
 * PLUNKER VERSION
 * (based on systemjs.config.js in angular.io)
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      tsconfig: true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },
    paths: {
      // paths serve as alias
      'npm:': 'https://unpkg.com/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',

      // angular bundles
      '@angular/core': 'npm:@angular/core@2.2.4/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common@2.2.4/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler@2.2.4/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser@2.2.4/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@2.2.4/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http@2.2.4/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router@3.0.1/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms@2.2.4/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade@2.2.4/bundles/upgrade.umd.js',

      // other libraries
      'rxjs':                       'npm:rxjs@5.0.0-beta.12',
      'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api@0.0.20',
      'ts':                         'npm:plugin-typescript@4.0.10/lib/plugin.js',
      'typescript':                 'npm:typescript@2.0.2/lib/typescript.js',
      '@ngrx/core':                 'npm:@ngrx/core@1.2.0/bundles/core.umd.js',
      '@ngrx/core/operator/select': 'npm:@ngrx/core@1.2.0/bundles/core.umd.js',
      '@ngrx/core/compose':         'npm:@ngrx/core@1.2.0/bundles/core.umd.js',
      '@ngrx/store':                'npm:@ngrx/store@2.2.1/bundles/store.umd.js',
      '@ngrx/effects':              'npm:@ngrx/@ngrx/store@2.2.1',
      '@ngrx/store-devtools':       'npm:@ngrx/store-devtools@3.1.0/bundles/store-devtools.umd.js',
      '@ngrx/store-log-monitor':    'npm:@ngrx/store-log-monitor@3.0.2/bundles/store-log-monitor.umd.js'
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.ts',
        defaultExtension: 'ts'
      },
      rxjs: {
        defaultExtension: 'js'
      },
      'angular2-in-memory-web-api': {
        main: './index.js',
        defaultExtension: 'js'
      }
    }
  });
})(this);
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true
  }
}
export interface IUser {
  // from server
  username: string;
  email: string;
  
  // for UI
  isConnecting: boolean;
  isConnected: boolean;
};
export const UserActions = {
  // when the user clicks on login button, before we launch the HTTP request
  // this will allow us to disable the login button during the request
  USR_IS_CONNECTING: 'USR_IS_CONNECTING',
  // this allows us to save the username and email of the user
  // we assume those data were fetched in the previous request
  USR_IS_CONNECTED: 'USR_IS_CONNECTED',

  // same pattern for disconnecting the user
  USR_IS_DISCONNECTING: 'USR_IS_DISCONNECTING',
  USR_IS_DISCONNECTED: 'USR_IS_DISCONNECTED'
};
export const UserFactory: IUser = () => {
  return {
    // from server
    username: null,
    email: null,

    // for UI
    isConnecting: false,
    isConnected: false,
    isDisconnecting: false
  };
};
import { UserActions } from './user.actions';
import { UserFactory } from './user.state';

export const UserReducer: ActionReducer<IUser> = (user: IUser = UserFactory(), action: Action) => {
  switch (action.type) {
    case UserActions.USR_IS_CONNECTING:
      return Object.assign({}, user, {
        isConnecting: true
      });

    case UserActions.USR_IS_CONNECTED:
      return Object.assign({}, user, {
        isConnecting: false,
        isConnected: true,
        username: action.payload.username,
        email: action.payload.email
      });

    case UserActions.USR_IS_DISCONNECTING:
      return Object.assign({}, user, {
        isDisconnecting: true
      });

    case UserActions.USR_IS_DISCONNECTED:
      return Object.assign({}, user, {
        isDisconnecting: false,
        isConnected: false,
        username: null,
        email: null
      });
      
    default:
      return user;
  }
};
import { IUser } from './user.interface';

export interface AppState {
  user: IUser;
}
import { Component, Input } from '@angular/core';

import { Store } from '@ngrx/store';

@Component({
  selector: 'user',
  styles: [
    '.table { max-width: 250px; }',
    '.truthy { color: green; font-weight: bold; }',
    '.falsy { color: red; }'
  ],
  template: `
    <h2>User information :</h2>
    
    <table class="table">
      <tr>
        <th>Property</th>
        <th>Value</th>
      </tr>
      
      <tr>
        <td>username</td>
        <td [class.truthy]="user.username" [class.falsy]="!user.username">
          {{ user.username ? user.username : 'null' }}
        </td>
      </tr>
      
      <tr>
        <td>email</td>
        <td [class.truthy]="user.email" [class.falsy]="!user.email">
          {{ user.email ? user.email : 'null' }}
        </td>
      </tr>
      
      <tr>
        <td>isConnecting</td>
        <td [class.truthy]="user.isConnecting" [class.falsy]="!user.isConnecting">
          {{ user.isConnecting }}
        </td>
      </tr>
      
      <tr>
        <td>isConnected</td>
        <td [class.truthy]="user.isConnected" [class.falsy]="!user.isConnected">
          {{ user.isConnected }}
        </td>
      </tr>
      
      <tr>
        <td>isDisconnecting</td>
        <td [class.truthy]="user.isDisconnecting" [class.falsy]="!user.isDisconnecting">
          {{ user.isDisconnecting }}
        </td>
      </tr>
    </table>
  `
})
export class UserComponent {
  @Input() user;
  
  constructor() { }
}
import { Component, Input } from '@angular/core';
import { Store } from '@ngrx/store';

import { AppState } from './app.state';
import { UserActions } from './user.actions';
import { UserService } from './user.service';

@Component({
  selector: 'login',
  styles: [
    'input[type="text"] { width: 160px; display: inline-block; }'
  ],
  template: `
    <form
      *ngIf="!(user | async).isConnected"
      #loginForm="ngForm"
      (ngSubmit)="login(loginForm.value.username)"
      class="form-inline"
    >
      <input
        type="text"
        class="form-control"
        name="username"
        placeholder="Username"
        [disabled]="(user | async).isConnecting"
        ngModel
      >
      
      <button
        type="submit"
        class="btn btn-primary"
        [disabled]="(user | async).isConnecting || (user | async).isConnected"
      >Log me in</button>
    </form>
    
    <button
      *ngIf="(user | async).isConnected"
      (click)="logout()"
      class="btn btn-primary"
      [disabled]="(user | async).isDisconnecting"
    >Log me out</button>
  `
})
export class LoginComponent {
  public user: Observable<IUser>;
  
  constructor(public store$: Store<AppState>, private userService: UserService) {
      this.user = store$.select('user');
  }
  
  login(username: string) {
    this.userService.login(username);
  }
  
  logout() {
    this.userService.logout();
  }
}
import { Injectable } from '@angular/core';

import { Store } from '@ngrx/store';

import { AppState } from './app.state';
import { UserActions } from './user.actions';

@Injectable()
export class UserService {
  constructor(public store$: Store<AppState>) { }
  
  login(username: string) {
    // first, dispatch an action saying that the user's tyring to connect
    // so we can lock the button until the HTTP request finish
    this.store$.dispatch({ type: UserActions.USR_IS_CONNECTING });
    
    // simulate some delay like we would have with an HTTP request
    // by using a timeout
    setTimeout(() => {
      // some email (or data) that you'd have get as HTTP response
      let email = `${username}@email.com`;
      
      this.store$.dispatch({ type: UserActions.USR_IS_CONNECTED, payload: { username, email } });
    }, 2000);
  }
  
  logout() {
    // first, dispatch an action saying that the user's tyring to connect
    // so we can lock the button until the HTTP request finish
    this.store$.dispatch({ type: UserActions.USR_IS_DISCONNECTING });
    
    // simulate some delay like we would have with an HTTP request
    // by using a timeout
    setTimeout(() => {
      this.store$.dispatch({ type: UserActions.USR_IS_DISCONNECTED });
    }, 2000);
  }
}