| @@ -9,7 +9,7 @@ import { SharedModule } from './shared/shared.module'; | |||
| import { ShoppingListModule } from './shopping-list/shopping-list.module'; | |||
| import { AuthModule } from './auth/auth.module'; | |||
| import { CoreModule } from './core/core.module'; | |||
| import { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers'; | |||
| import { appReducers } from './ngrx/app.reducers'; | |||
| @NgModule({ | |||
| declarations: [ | |||
| @@ -23,7 +23,7 @@ import { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers | |||
| ShoppingListModule, | |||
| AuthModule, | |||
| CoreModule, | |||
| StoreModule.forRoot({shoppingList: shoppingListReducer}) | |||
| StoreModule.forRoot(appReducers) | |||
| ], | |||
| bootstrap: [AppComponent] | |||
| }) | |||
| @@ -1,14 +1,20 @@ | |||
| import { CanActivate } from "@angular/router"; | |||
| import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router/src/router_state"; | |||
| import { Injectable } from "@angular/core"; | |||
| import { AuthService } from "./auth.service"; | |||
| import { Store } from "@ngrx/store"; | |||
| import "rxjs/add/operator/map"; | |||
| import * as fromApp from '../ngrx/app.reducers'; | |||
| import * as fromAuth from './ngrx/auth.reducers'; | |||
| @Injectable() | |||
| export class AuthGuard implements CanActivate { | |||
| constructor(private authService: AuthService) {} | |||
| constructor(private store: Store<fromApp.AppState>) {} | |||
| canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { | |||
| return this.authService.isAuthenticated(); | |||
| return this.store.select('auth').map((authState: fromAuth.State) => { | |||
| return authState.authenticated; | |||
| }); | |||
| } | |||
| } | |||
| @@ -1,15 +1,30 @@ | |||
| import * as firebase from 'firebase'; | |||
| import { Router } from '@angular/router'; | |||
| import { Injectable } from '@angular/core'; | |||
| import { Store } from '@ngrx/store'; | |||
| import * as fromApp from '../ngrx/app.reducers'; | |||
| import * as AuthActions from './ngrx/auth.actions'; | |||
| @Injectable() | |||
| export class AuthService { | |||
| token: string; | |||
| constructor(private router: Router) {} | |||
| constructor(private router: Router, private store: Store<fromApp.AppState>) {} | |||
| signupUser(email: string, password: string) { | |||
| firebase.auth().createUserWithEmailAndPassword(email, password).catch( | |||
| firebase.auth().createUserWithEmailAndPassword(email, password) | |||
| .then( | |||
| user => { | |||
| this.store.dispatch(new AuthActions.Signup()); | |||
| firebase.auth().currentUser.getIdToken() | |||
| .then( | |||
| (token: string) => { | |||
| this.store.dispatch(new AuthActions.SetToken(token)); | |||
| } | |||
| ); | |||
| } | |||
| ) | |||
| .catch( | |||
| error => console.log(error) | |||
| ); | |||
| } | |||
| @@ -18,10 +33,13 @@ export class AuthService { | |||
| firebase.auth().signInWithEmailAndPassword(email, password) | |||
| .then( | |||
| response => { | |||
| this.store.dispatch(new AuthActions.Signin()); | |||
| this.router.navigate(['/']); | |||
| firebase.auth().currentUser.getIdToken() | |||
| .then( | |||
| (token: string) => this.token = token | |||
| (token: string) => { | |||
| this.store.dispatch(new AuthActions.SetToken(token)); | |||
| } | |||
| ); | |||
| } | |||
| ) | |||
| @@ -32,19 +50,7 @@ export class AuthService { | |||
| logout() { | |||
| firebase.auth().signOut(); | |||
| this.token = null; | |||
| this.store.dispatch(new AuthActions.Logout()); | |||
| this.router.navigate(['/']); | |||
| } | |||
| getToken() { | |||
| firebase.auth().currentUser.getIdToken() | |||
| .then( | |||
| (token: string) => this.token = token | |||
| ); | |||
| return this.token; | |||
| } | |||
| isAuthenticated() { | |||
| return this.token != null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| import { Action } from '@ngrx/store'; | |||
| export const SIGNUP = 'SIGNUP'; | |||
| export const SIGNIN = 'SIGNIN'; | |||
| export const LOGOUT = 'LOGOUT'; | |||
| export const SET_TOKEN = 'SET_TOKEN'; | |||
| export class Signup implements Action { | |||
| readonly type = SIGNUP; | |||
| } | |||
| export class Signin implements Action { | |||
| readonly type = SIGNIN; | |||
| } | |||
| export class Logout implements Action { | |||
| readonly type = LOGOUT; | |||
| } | |||
| export class SetToken implements Action { | |||
| readonly type = SET_TOKEN; | |||
| constructor(public payload: string) {} | |||
| } | |||
| export type AuthActions = Signup | Signin | Logout | SetToken; | |||
| @@ -0,0 +1,35 @@ | |||
| import * as AuthActions from './auth.actions'; | |||
| export interface State { | |||
| token: string; | |||
| authenticated: boolean; | |||
| } | |||
| const initialState: State = { | |||
| token: null, | |||
| authenticated: false | |||
| }; | |||
| export function authReducers(state = initialState, action: AuthActions.AuthActions) { | |||
| switch (action.type) { | |||
| case AuthActions.SIGNUP: | |||
| case AuthActions.SIGNIN: | |||
| return { | |||
| ...state, | |||
| authenticated: true | |||
| }; | |||
| case AuthActions.LOGOUT: | |||
| return { | |||
| ...state, | |||
| token: null, | |||
| authenticated: false | |||
| }; | |||
| case AuthActions.SET_TOKEN: | |||
| return { | |||
| ...state, | |||
| token: action.payload | |||
| }; | |||
| default: | |||
| return state; | |||
| } | |||
| } | |||
| @@ -10,12 +10,12 @@ | |||
| <li routerLinkActive="active"><a routerLink="/shopping-list">Shopping List</a></li> | |||
| </ul> | |||
| <ul class="nav navbar-nav navbar-right"> | |||
| <ng-template [ngIf]="!isAuthenticated()"> | |||
| <ng-template [ngIf]="!(authState | async).authenticated"> | |||
| <li><a routerLink="/signup">Register</a></li> | |||
| <li><a routerLink="/signin">Login</a></li> | |||
| </ng-template> | |||
| <li><a style="cursor: pointer;" (click)="onLogout()" *ngIf="isAuthenticated()">Logout</a></li> | |||
| <li class="dropdown" appDropdown *ngIf="isAuthenticated()"> | |||
| <li><a style="cursor: pointer;" (click)="onLogout()" *ngIf="(authState | async).authenticated">Logout</a></li> | |||
| <li class="dropdown" appDropdown *ngIf="(authState | async).authenticated"> | |||
| <a style="cursor: pointer;" class="dropdown-toggle" role="button">Manage <span class="caret"></span></a> | |||
| <ul class="dropdown-menu"> | |||
| <li><a style="cursor: pointer;" (click)="onSaveData()">Save</a></li> | |||
| @@ -1,14 +1,26 @@ | |||
| import { Component } from "@angular/core"; | |||
| import { Component, OnInit } from "@angular/core"; | |||
| import { Store } from "@ngrx/store"; | |||
| import { Observable } from "rxjs/Observable"; | |||
| import { DataStorageService } from "../../shared/data-storage.service"; | |||
| import { AuthService } from "../../auth/auth.service"; | |||
| import * as fromApp from '../../ngrx/app.reducers'; | |||
| import * as fromAuth from '../../auth/ngrx/auth.reducers'; | |||
| @Component({ | |||
| selector: 'app-header', | |||
| templateUrl: './header.component.html' | |||
| }) | |||
| export class HeaderComponent { | |||
| export class HeaderComponent implements OnInit { | |||
| authState: Observable<fromAuth.State>; | |||
| constructor(private dataStorageService: DataStorageService, | |||
| private authService: AuthService, | |||
| private store: Store<fromApp.AppState>) {} | |||
| constructor(private dataStorageService: DataStorageService, private authService: AuthService) {} | |||
| ngOnInit() { | |||
| this.authState = this.store.select('auth') | |||
| } | |||
| onSaveData() { | |||
| this.dataStorageService.storeRecipes().subscribe( | |||
| @@ -25,8 +37,4 @@ export class HeaderComponent { | |||
| onLogout() { | |||
| this.authService.logout(); | |||
| } | |||
| isAuthenticated() { | |||
| return this.authService.isAuthenticated(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| import { ActionReducerMap } from '@ngrx/store/src/models'; | |||
| import * as fromShoppingList from '../shopping-list/ngrx/shopping-list.reducers' | |||
| import * as fromAuth from '../auth/ngrx/auth.reducers' | |||
| export interface AppState { | |||
| shoppingList: fromShoppingList.State, | |||
| auth: fromAuth.State | |||
| } | |||
| export const appReducers: ActionReducerMap<AppState> = { | |||
| shoppingList: fromShoppingList.shoppingListReducer, | |||
| auth: fromAuth.authReducers | |||
| }; | |||
| @@ -5,7 +5,7 @@ import { Store } from '@ngrx/store'; | |||
| import { Recipe } from '../recipe.model'; | |||
| import { RecipeService } from '../recipe.service'; | |||
| import * as ShoppingListActions from '../../shopping-list/ngrx/shopping-list.actions'; | |||
| import * as fromShoppingList from '../../shopping-list/ngrx/shopping-list.reducers'; | |||
| import * as fromApp from '../../ngrx/app.reducers'; | |||
| @Component({ | |||
| selector: 'app-recipe-detail', | |||
| @@ -19,7 +19,7 @@ export class RecipeDetailComponent implements OnInit { | |||
| constructor(private recipeService: RecipeService, | |||
| private route: ActivatedRoute, | |||
| private router: Router, | |||
| private store: Store<fromShoppingList.AppState>) { } | |||
| private store: Store<fromApp.AppState>) { } | |||
| ngOnInit() { | |||
| this.route.params.subscribe( | |||
| @@ -1,16 +1,23 @@ | |||
| import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http' | |||
| import { Observable } from 'rxjs/Observable'; | |||
| import { Injectable } from '@angular/core'; | |||
| import { AuthService } from '../auth/auth.service'; | |||
| import { Store } from "@ngrx/store"; | |||
| import "rxjs/add/operator/switchMap"; | |||
| import "rxjs/add/operator/take"; | |||
| import * as fromApp from '../ngrx/app.reducers'; | |||
| import * as fromAuth from '../auth/ngrx/auth.reducers'; | |||
| @Injectable() | |||
| export class AuthInterceptor implements HttpInterceptor { | |||
| constructor(private authService: AuthService) {} | |||
| constructor(private store: Store<fromApp.AppState>) {} | |||
| intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | |||
| const copiedReq = req.clone({ | |||
| params: req.params.set('auth', this.authService.getToken()) | |||
| }); | |||
| return next.handle(copiedReq); | |||
| return this.store.select('auth') | |||
| .take(1) | |||
| .switchMap((authState: fromAuth.State) => { | |||
| const copiedReq = req.clone({params: req.params.set('auth', authState.token)}); | |||
| return next.handle(copiedReq); | |||
| }); | |||
| } | |||
| } | |||
| @@ -2,21 +2,12 @@ import * as ShoppingListActions from './shopping-list.actions'; | |||
| import { Ingredient } from '../../shared/ingredient.model'; | |||
| export interface AppState { | |||
| shoppingList: State | |||
| } | |||
| export interface State { | |||
| ingredients: Ingredient[]; | |||
| editedIngredient: Ingredient; | |||
| editedIngredientIndex: number; | |||
| } | |||
| export const ADD_INGREDIENT = 'ADD_INGREDIENT'; | |||
| export const ADD_INGREDIENTS = 'ADD_INGREDIENTS'; | |||
| export const UPDATE_INGREDIENT = 'UPDATE_INGREDIENT'; | |||
| export const DELETE_INGREDIENT = 'DELETE_INGREDIENT'; | |||
| const initialState: State = { | |||
| ingredients: [ | |||
| new Ingredient('Banana', 10) | |||
| @@ -5,7 +5,7 @@ import { Store } from '@ngrx/store'; | |||
| import { Ingredient } from '../../shared/ingredient.model'; | |||
| import * as ShoppingListActions from '../ngrx/shopping-list.actions'; | |||
| import * as fromShoppingList from '../ngrx/shopping-list.reducers'; | |||
| import * as fromApp from '../../ngrx/app.reducers' | |||
| @Component({ | |||
| selector: 'app-shopping-edit', | |||
| @@ -18,7 +18,7 @@ export class ShoppingEditComponent implements OnInit, OnDestroy { | |||
| editMode = false; | |||
| editedItem: Ingredient; | |||
| constructor(private store: Store<fromShoppingList.AppState>) { } | |||
| constructor(private store: Store<fromApp.AppState>) { } | |||
| ngOnInit() { | |||
| this.subscription = this.store.select('shoppingList').subscribe( | |||
| @@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable'; | |||
| import { Ingredient } from '../shared/ingredient.model'; | |||
| import * as ShoppingListActions from './ngrx/shopping-list.actions'; | |||
| import * as fromShoppingList from './ngrx/shopping-list.reducers'; | |||
| import * as fromApp from '../ngrx/app.reducers' | |||
| @Component({ | |||
| selector: 'app-shopping-list', | |||
| @@ -14,7 +14,7 @@ import * as fromShoppingList from './ngrx/shopping-list.reducers'; | |||
| export class ShoppingListComponent implements OnInit { | |||
| shoppingListState: Observable<{ingredients: Ingredient[]}>; | |||
| constructor(private store: Store<fromShoppingList.AppState>) { } | |||
| constructor(private store: Store<fromApp.AppState>) { } | |||
| ngOnInit() { | |||
| this.shoppingListState = this.store.select('shoppingList'); | |||