| @@ -9,7 +9,7 @@ import { SharedModule } from './shared/shared.module'; | |||||
| import { ShoppingListModule } from './shopping-list/shopping-list.module'; | import { ShoppingListModule } from './shopping-list/shopping-list.module'; | ||||
| import { AuthModule } from './auth/auth.module'; | import { AuthModule } from './auth/auth.module'; | ||||
| import { CoreModule } from './core/core.module'; | import { CoreModule } from './core/core.module'; | ||||
| import { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers'; | |||||
| import { appReducers } from './ngrx/app.reducers'; | |||||
| @NgModule({ | @NgModule({ | ||||
| declarations: [ | declarations: [ | ||||
| @@ -23,7 +23,7 @@ import { shoppingListReducer } from './shopping-list/ngrx/shopping-list.reducers | |||||
| ShoppingListModule, | ShoppingListModule, | ||||
| AuthModule, | AuthModule, | ||||
| CoreModule, | CoreModule, | ||||
| StoreModule.forRoot({shoppingList: shoppingListReducer}) | |||||
| StoreModule.forRoot(appReducers) | |||||
| ], | ], | ||||
| bootstrap: [AppComponent] | bootstrap: [AppComponent] | ||||
| }) | }) | ||||
| @@ -1,14 +1,20 @@ | |||||
| import { CanActivate } from "@angular/router"; | import { CanActivate } from "@angular/router"; | ||||
| import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router/src/router_state"; | import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router/src/router_state"; | ||||
| import { Injectable } from "@angular/core"; | 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() | @Injectable() | ||||
| export class AuthGuard implements CanActivate { | export class AuthGuard implements CanActivate { | ||||
| constructor(private authService: AuthService) {} | |||||
| constructor(private store: Store<fromApp.AppState>) {} | |||||
| canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { | 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 * as firebase from 'firebase'; | ||||
| import { Router } from '@angular/router'; | import { Router } from '@angular/router'; | ||||
| import { Injectable } from '@angular/core'; | 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() | @Injectable() | ||||
| export class AuthService { | export class AuthService { | ||||
| token: string; | |||||
| constructor(private router: Router) {} | |||||
| constructor(private router: Router, private store: Store<fromApp.AppState>) {} | |||||
| signupUser(email: string, password: string) { | 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) | error => console.log(error) | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -18,10 +33,13 @@ export class AuthService { | |||||
| firebase.auth().signInWithEmailAndPassword(email, password) | firebase.auth().signInWithEmailAndPassword(email, password) | ||||
| .then( | .then( | ||||
| response => { | response => { | ||||
| this.store.dispatch(new AuthActions.Signin()); | |||||
| this.router.navigate(['/']); | this.router.navigate(['/']); | ||||
| firebase.auth().currentUser.getIdToken() | firebase.auth().currentUser.getIdToken() | ||||
| .then( | .then( | ||||
| (token: string) => this.token = token | |||||
| (token: string) => { | |||||
| this.store.dispatch(new AuthActions.SetToken(token)); | |||||
| } | |||||
| ); | ); | ||||
| } | } | ||||
| ) | ) | ||||
| @@ -32,19 +50,7 @@ export class AuthService { | |||||
| logout() { | logout() { | ||||
| firebase.auth().signOut(); | firebase.auth().signOut(); | ||||
| this.token = null; | |||||
| this.store.dispatch(new AuthActions.Logout()); | |||||
| this.router.navigate(['/']); | 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> | <li routerLinkActive="active"><a routerLink="/shopping-list">Shopping List</a></li> | ||||
| </ul> | </ul> | ||||
| <ul class="nav navbar-nav navbar-right"> | <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="/signup">Register</a></li> | ||||
| <li><a routerLink="/signin">Login</a></li> | <li><a routerLink="/signin">Login</a></li> | ||||
| </ng-template> | </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> | <a style="cursor: pointer;" class="dropdown-toggle" role="button">Manage <span class="caret"></span></a> | ||||
| <ul class="dropdown-menu"> | <ul class="dropdown-menu"> | ||||
| <li><a style="cursor: pointer;" (click)="onSaveData()">Save</a></li> | <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 { DataStorageService } from "../../shared/data-storage.service"; | ||||
| import { AuthService } from "../../auth/auth.service"; | import { AuthService } from "../../auth/auth.service"; | ||||
| import * as fromApp from '../../ngrx/app.reducers'; | |||||
| import * as fromAuth from '../../auth/ngrx/auth.reducers'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-header', | selector: 'app-header', | ||||
| templateUrl: './header.component.html' | 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() { | onSaveData() { | ||||
| this.dataStorageService.storeRecipes().subscribe( | this.dataStorageService.storeRecipes().subscribe( | ||||
| @@ -25,8 +37,4 @@ export class HeaderComponent { | |||||
| onLogout() { | onLogout() { | ||||
| this.authService.logout(); | 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 { Recipe } from '../recipe.model'; | ||||
| import { RecipeService } from '../recipe.service'; | import { RecipeService } from '../recipe.service'; | ||||
| import * as ShoppingListActions from '../../shopping-list/ngrx/shopping-list.actions'; | 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({ | @Component({ | ||||
| selector: 'app-recipe-detail', | selector: 'app-recipe-detail', | ||||
| @@ -19,7 +19,7 @@ export class RecipeDetailComponent implements OnInit { | |||||
| constructor(private recipeService: RecipeService, | constructor(private recipeService: RecipeService, | ||||
| private route: ActivatedRoute, | private route: ActivatedRoute, | ||||
| private router: Router, | private router: Router, | ||||
| private store: Store<fromShoppingList.AppState>) { } | |||||
| private store: Store<fromApp.AppState>) { } | |||||
| ngOnInit() { | ngOnInit() { | ||||
| this.route.params.subscribe( | this.route.params.subscribe( | ||||
| @@ -1,16 +1,23 @@ | |||||
| import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http' | import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http' | ||||
| import { Observable } from 'rxjs/Observable'; | import { Observable } from 'rxjs/Observable'; | ||||
| import { Injectable } from '@angular/core'; | 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() | @Injectable() | ||||
| export class AuthInterceptor implements HttpInterceptor { | export class AuthInterceptor implements HttpInterceptor { | ||||
| constructor(private authService: AuthService) {} | |||||
| constructor(private store: Store<fromApp.AppState>) {} | |||||
| intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | 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'; | import { Ingredient } from '../../shared/ingredient.model'; | ||||
| export interface AppState { | |||||
| shoppingList: State | |||||
| } | |||||
| export interface State { | export interface State { | ||||
| ingredients: Ingredient[]; | ingredients: Ingredient[]; | ||||
| editedIngredient: Ingredient; | editedIngredient: Ingredient; | ||||
| editedIngredientIndex: number; | 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 = { | const initialState: State = { | ||||
| ingredients: [ | ingredients: [ | ||||
| new Ingredient('Banana', 10) | new Ingredient('Banana', 10) | ||||
| @@ -5,7 +5,7 @@ import { Store } from '@ngrx/store'; | |||||
| import { Ingredient } from '../../shared/ingredient.model'; | import { Ingredient } from '../../shared/ingredient.model'; | ||||
| import * as ShoppingListActions from '../ngrx/shopping-list.actions'; | import * as ShoppingListActions from '../ngrx/shopping-list.actions'; | ||||
| import * as fromShoppingList from '../ngrx/shopping-list.reducers'; | |||||
| import * as fromApp from '../../ngrx/app.reducers' | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-shopping-edit', | selector: 'app-shopping-edit', | ||||
| @@ -18,7 +18,7 @@ export class ShoppingEditComponent implements OnInit, OnDestroy { | |||||
| editMode = false; | editMode = false; | ||||
| editedItem: Ingredient; | editedItem: Ingredient; | ||||
| constructor(private store: Store<fromShoppingList.AppState>) { } | |||||
| constructor(private store: Store<fromApp.AppState>) { } | |||||
| ngOnInit() { | ngOnInit() { | ||||
| this.subscription = this.store.select('shoppingList').subscribe( | this.subscription = this.store.select('shoppingList').subscribe( | ||||
| @@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable'; | |||||
| import { Ingredient } from '../shared/ingredient.model'; | import { Ingredient } from '../shared/ingredient.model'; | ||||
| import * as ShoppingListActions from './ngrx/shopping-list.actions'; | import * as ShoppingListActions from './ngrx/shopping-list.actions'; | ||||
| import * as fromShoppingList from './ngrx/shopping-list.reducers'; | |||||
| import * as fromApp from '../ngrx/app.reducers' | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-shopping-list', | selector: 'app-shopping-list', | ||||
| @@ -14,7 +14,7 @@ import * as fromShoppingList from './ngrx/shopping-list.reducers'; | |||||
| export class ShoppingListComponent implements OnInit { | export class ShoppingListComponent implements OnInit { | ||||
| shoppingListState: Observable<{ingredients: Ingredient[]}>; | shoppingListState: Observable<{ingredients: Ingredient[]}>; | ||||
| constructor(private store: Store<fromShoppingList.AppState>) { } | |||||
| constructor(private store: Store<fromApp.AppState>) { } | |||||
| ngOnInit() { | ngOnInit() { | ||||
| this.shoppingListState = this.store.select('shoppingList'); | this.shoppingListState = this.store.select('shoppingList'); | ||||