Skip to content

Commit

Permalink
Refactor ngrx store functionality to display a value from the store a…
Browse files Browse the repository at this point in the history
…nd save it in local storage after pushing a button
  • Loading branch information
Tommy Heyding committed Apr 10, 2022
1 parent 3686372 commit fa62c2a
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 73 deletions.
4 changes: 2 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {AppRoutingModule} from './app-routing.module';
import {StoreModule} from '@ngrx/store';
import {EffectsModule} from '@ngrx/effects';
import * as fromApp from './app.reducer';
import {metaReducers, reducers} from './app.reducer';
import {StoreDevtoolsModule} from '@ngrx/store-devtools';
import {environment} from '../environments/environment';

Expand All @@ -34,7 +34,7 @@ import {environment} from '../environments/environment';
}
}),
AppRoutingModule,
StoreModule.forRoot(fromApp.appReducer),
StoreModule.forRoot(reducers, {metaReducers}),
EffectsModule.forRoot([]),
StoreDevtoolsModule.instrument({maxAge: 25, logOnly: environment.production})
],
Expand Down
29 changes: 22 additions & 7 deletions src/app/app.reducer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import {ActionReducerMap} from '@ngrx/store';
import * as fromHome from './home/pages/home/store/home.reducer';
import {ActionReducer, ActionReducerMap, combineReducers, MetaReducer} from '@ngrx/store';
import {AppStore} from './app.store';
import {localStorageSync} from 'ngrx-store-localstorage';
import {homeFeatureKey} from './home/pages/home/store/home.store';
import {homeReducer} from './home/pages/home/store/home.reducer';

export interface AppState {
home: fromHome.HomeState;
// There seems to be a bug with redux. This declaration leads to compile errors. The option to use combineReducers works.
/*
export const reducers: ActionReducerMap<AppStore> = {
home: homeReducer,
};*/

export const reducers = combineReducers({homeReducer} as any);

export function localStorageSyncReducer(reducer: ActionReducer<AppStore>): ActionReducer<AppStore> {
return localStorageSync({
keys: [
homeFeatureKey
],
rehydrate: true,
storage: window.sessionStorage
})(reducer);
}

export const appReducer: ActionReducerMap<AppState, any> = {
home: fromHome.homeReducer
};
export const metaReducers: Array<MetaReducer<any, any>> = [localStorageSyncReducer];
5 changes: 5 additions & 0 deletions src/app/app.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {HomeState} from './home/pages/home/store/home.store';

export interface AppStore {
home?: HomeState;
}
5 changes: 3 additions & 2 deletions src/app/home/home.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {PageNotFoundComponent} from './components/page-not-found/page-not-found.
import {RouterModule} from '@angular/router';
import {FeatureOverviewComponent} from './components/feature-overview/feature-overview.component';
import {StoreModule} from '@ngrx/store';
import * as fromHome from './pages/home/store/home.reducer';
import {EffectsModule} from '@ngrx/effects';
import {HomeEffects} from './pages/home/store/home.effects';
import {FormsModule} from '@angular/forms';
import {homeFeatureKey} from './pages/home/store/home.store';
import {homeReducer} from './pages/home/store/home.reducer';


@NgModule({
Expand All @@ -29,7 +30,7 @@ import {FormsModule} from '@angular/forms';
CommonModule,
TranslateModule,
RouterModule,
StoreModule.forFeature(fromHome.homeFeatureKey, fromHome.homeReducer),
StoreModule.forFeature(homeFeatureKey, homeReducer),
EffectsModule.forFeature([HomeEffects]),
FormsModule
]
Expand Down
22 changes: 16 additions & 6 deletions src/app/home/pages/home/home.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,29 @@ <h2 class="text-3xl font-extrabold text-gray-900">{{'home.ngrx.title' | translat
<p class="mt-4 text-lg text-gray-500">{{'home.ngrx.description' | translate}}</p>
</div>
<div class="max-w-3xl mx-auto pt-6">
<p
class="block text-xl font-medium text-gray-700 py-2">{{'home.ngrx.store' | translate}}{{userInput$ | async}}</p>
<div>
<div class="mt-1">
<textarea [(ngModel)]="userInput" (ngModelChange)="hideUserInput()" rows="4"
<textarea rows="4" [(ngModel)]="userInput" (ngModelChange)="hideUserInput()"
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full border-gray-300 rounded-md"></textarea>
<div class="mt-8 flex justify-center">
<button type="button" (click)="storeUserInput()"
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-indigo-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
{{'home.ngrx.button' | translate}}
</button>
<div class="inline-flex rounded-md shadow">
<button type="button" (click)="storeUserInput(userInput!)"
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-indigo-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
{{'home.ngrx.button.store' | translate}}
</button>
</div>
<div class="ml-3 inline-flex">
<button type="button" (click)="resetStore()"
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-indigo-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
{{'home.ngrx.button.reset' | translate}}
</button>
</div>
</div>
</div>
<p *ngIf="displayUserInput"
class="block text-xl font-medium text-gray-700 pt-6">{{'home.ngrx.message' | translate}}{{userInput}}</p>
class="block text-xl font-medium text-gray-700 pt-6">{{'home.ngrx.message' | translate}}{{userInput$ | async}}</p>
</div>
</div>
</div>
Expand Down
26 changes: 15 additions & 11 deletions src/app/home/pages/home/home.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Component, OnInit} from '@angular/core';
import {map, Subscription} from 'rxjs';
import {Observable} from 'rxjs';
import {Store} from '@ngrx/store';
import * as HomeActions from './store/home.actions';
import * as fromApp from '../../../app.reducer'
import {HomeSelectors} from './store/home.selectors';
import {HomeActions} from './store/home.actions';

@Component({
selector: 'app-home',
Expand All @@ -11,23 +11,27 @@ import * as fromApp from '../../../app.reducer'
})
export class HomeComponent implements OnInit {

userInput$: Observable<string> | undefined;
userInput: string | undefined;
userInputSubscription: Subscription | undefined;
displayUserInput: boolean = false;

constructor(private store: Store<fromApp.AppState>) {
constructor(private store: Store) {
}

ngOnInit(): void {
this.userInputSubscription = this.store.select('home').pipe(map(homeState => homeState.userInput)).subscribe((userInput: string) => {
this.userInput = userInput;
});
this.userInput$ = this.store.select(HomeSelectors.selectUserInput);
}

storeUserInput() {
storeUserInput(userInput: string) {
this.displayUserInput = true;
this.store.dispatch(new HomeActions.SetUserInput(this.userInput!));
this.userInputSubscription?.unsubscribe();
this.store.dispatch(HomeActions.setUserInput({userInput}));
this.userInput = '';
}

resetStore() {
this.displayUserInput = false;
this.store.dispatch(HomeActions.setUserInput({userInput: 'Hello world!'}));
this.userInput = '';
}

hideUserInput(): void {
Expand Down
24 changes: 9 additions & 15 deletions src/app/home/pages/home/store/home.actions.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import {Action} from '@ngrx/store';
import {createAction, props} from '@ngrx/store';

// Load Data is just a dummy action and is not used
export const LOAD_DATA = '[HOME] LOAD_DATA';
export const SET_USER_INPUT = '[HOME] SET_USER_INPUT';

export class LoadData implements Action {
readonly type = LOAD_DATA;
const loadData = createAction('[HOME] LOAD DATA');
const getUserInput = createAction('[HOME] GET USER INPUT');
const setUserInput = createAction('[HOME] SET USER INPUT', props<{ userInput: string }>());

export const HomeActions = {
loadData,
getUserInput,
setUserInput
}

export class SetUserInput implements Action {
readonly type = SET_USER_INPUT;

constructor(public payload: string) {
}
}

export type HomeActions = LoadData | SetUserInput
5 changes: 2 additions & 3 deletions src/app/home/pages/home/store/home.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import {Actions, createEffect, ofType} from '@ngrx/effects';

import {concatMap} from 'rxjs/operators';
import {EMPTY, Observable} from 'rxjs';

import * as HomeActions from './home.actions';
import {HomeActions} from './home.actions';


@Injectable()
Expand All @@ -13,7 +12,7 @@ export class HomeEffects {

loadData$ = createEffect(() => {
return this.actions$.pipe(
ofType(HomeActions.LOAD_DATA),
ofType(HomeActions.loadData),
/** An EMPTY observable only emits completion. Replace with your own observable API request */
concatMap(() => EMPTY as Observable<{ type: string }>)
);
Expand Down
27 changes: 7 additions & 20 deletions src/app/home/pages/home/store/home.reducer.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
import * as HomeActions from './home.actions';
import {Action, createReducer, on} from '@ngrx/store';
import {defaultHomeStore, HomeState} from './home.store';
import {HomeActions} from './home.actions';

export const homeFeatureKey = 'home';
const homeRedux = createReducer(defaultHomeStore,
on(HomeActions.setUserInput, (state, {userInput}) => ({...state, userInput})));

export interface HomeState {
userInput: string;
}

export const initialHomeState: HomeState = {
userInput: 'Hello world!'
};

export function homeReducer(state: HomeState = initialHomeState, action: HomeActions.HomeActions): HomeState {
switch (action.type) {
case HomeActions.SET_USER_INPUT:
return {
...state,
userInput: action.payload
};
default:
return state;
}
export function homeReducer(state: HomeState, action: Action): HomeState {
return homeRedux(state, action);
}
15 changes: 8 additions & 7 deletions src/app/home/pages/home/store/home.selectors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {createFeatureSelector, createSelector} from '@ngrx/store';
import * as fromHome from './home.reducer';
import {AppStore} from '../../../../app.store';
import {HomeState} from './home.store';

export const selectHomeState = createFeatureSelector<fromHome.HomeState>(
fromHome.homeFeatureKey
);
const selectFeature = createFeatureSelector<AppStore, HomeState>('home');

export const selectUserInput = createSelector(
selectHomeState, state => state.userInput
);
const selectUserInput = createSelector(selectFeature, homeState => homeState.userInput);

export const HomeSelectors = {
selectUserInput: selectUserInput,
};
9 changes: 9 additions & 0 deletions src/app/home/pages/home/store/home.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const homeFeatureKey = 'home';

export interface HomeState {
userInput: string;
}

export const defaultHomeStore: HomeState = {
userInput: 'Hello world!'
}

0 comments on commit fa62c2a

Please sign in to comment.