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);
}
}