I seem to have gotten stuck on this matter for the last couple of days.
We're working on an Angular 2 application, and I need to create a wizard for users to fill out a form.
I've successfully managed to make the data flow through each step of the wizard, and save it in order to freely move back and forth. However, I can't seem to be able to reset it once the form is submitted.
I should add that each component is behind a wall. Maybe a better solution would be a singleton service injected directly at the AppModule. But I can't seem to make it work.
Here's my code so far:
Step 1
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { EventOption } from '../../../events/shared/event-option.model';
import { Store } from '@ngrx/store';
import { NewEventService } from '../shared/new-event.service';
import { Event } from '../../../events/shared/event.model';
import { FriendService } from '../../../friends/shared/friend.service';
@Component({
selector: 'app-upload-images',
templateUrl: './upload-images.component.html',
styleUrls: ['../../../events/new-event/new-event.component.css']
})
export class UploadImagesComponent implements OnInit {
form: FormGroup;
private event;
private images = [];
constructor(
private _store: Store<any>,
private formBuilder: FormBuilder,
private router: Router,
private newEventService: NewEventService,
private friendService: FriendService
) {
_store.select('newEvent').subscribe(newEvent => {
this.event = newEvent;
})
}
ngOnInit() {
this.initForm(this.event);
if (this.event.counter === 0) {
let friends = this.friendService.getFriends('58aaf6304fabf427e0acc08d');
for (let friend in friends) {
this.event.userIds.push(friends[friend]['id']);
}
}
}
initForm(event: Event) {
this.images.push({ imageUrl: 'test0', voteCount: 0 });
this.images.push({ imageUrl: 'test1', voteCount: 0 });
this.images.push({ imageUrl: 'test2', voteCount: 0 });
this.images.push({ imageUrl: 'test3', voteCount: 0 });
this.form = this.formBuilder.group({
firstImage: [this.event.length > 0 ? this.event.eventOption[0].imageUrl : null],
secondImage: [this.event.length > 0 ? this.event.eventOption[1].imageUrl : null],
thirdImage: [this.event.length > 0 ? this.event.eventOption[2].imageUrl : null],
fourthImage: [this.event.length > 0 ? this.event.eventOption[3].imageUrl : null],
})
}
next() {
this.event.eventOptions = this.images;
this.newEventService.updateEvent(this.event);
this.router.navigate(['events/new-event/choose-friends']);
}
}
Step 2
import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { EventOption } from '../../../events/shared/event-option.model';
import { Store } from '@ngrx/store';
import { Event } from '../../shared/event.model';
import { NewEventService } from '../shared/new-event.service';
import { FriendService } from '../../../friends/shared/friend.service';
import { SearchPipe } from '../../../core/search.pipe';
@Component({
selector: 'app-choose-friends',
templateUrl: './choose-friends.component.html',
styleUrls: ['../../../events/new-event/new-event.component.css', './choose-friends.component.css']
})
export class ChooseFriendsComponent implements OnInit {
private searchTerm = '';
private event;
private friends = [];
private friendsError = false;
constructor(
private _store: Store<any>,
private formBuilder: FormBuilder,
private router: Router,
private newEventService: NewEventService,
private friendService: FriendService
) {
_store.select('newEvent').subscribe(newEvent => {
this.event = newEvent;
})
}
ngOnInit() {
this.friends = this.friendService.getFriends('58aaf6304fabf427e0acc08d');
}
selectedFriend(friendId: string) {
return this.friendService.selectedFriend(friendId, this.event.userIds);
}
toggleFriend(friendId: string) {
return this.friendService.toggleFriend(friendId, this.event.userIds);
}
toggleAllFriends() {
return this.friendService.toggleAllFriends(this.friends, this.event.userIds);
}
submit() {
if (this.event.userIds.length > 0) {
this.newEventService.resetEvent();
this.router.navigate(['events/vote-events']);
} else {
this.friendsError = true;
}
}
back() {
this.newEventService.updateEvent(this.event);
this.router.navigate(['events/new-event/upload-images']);
}
}
Event Service
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Store, Action } from '@ngrx/store';
import { Event } from '../../../events/shared/event.model';
import { EventOption } from '../../../events/shared/event-option.model';
import { newEvent, newEventModel } from './new-event.reducer';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/find';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class NewEventService {
public newEvent$: Observable<newEventModel>;
constructor(private store: Store<newEventModel>) {
this.newEvent$ = this.store.select('newEvent');
}
getEvent(event) {
return this.store.dispatch({
type: 'GET_EVENT',
payload: event
})
}
updateEvent(event) {
return this.store.dispatch({
type: 'UPDATE_EVENT',
payload: event
})
}
resetEvent() {
return this.store.dispatch({
type: 'RESET_EVENT',
})
}
}
Event Reducer
import { EventOption } from '../../shared/event-option.model';
import { EventType } from '../../shared/event-type.model';
import { ActionReducer, Action } from '@ngrx/store';
import { Event } from '../../shared/event.model';
import { FriendService } from '../../../friends/shared/friend.service';
export interface newEventModel {
eventOptions: EventOption[];
eventTypeId: number,
duration: number,
comment: string,
privacyId: number,
isGlobal: boolean,
id: string,
userIds: string[],
counter: number
}
let blankState: newEventModel = {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
}
let initialState: newEventModel = {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
}
export const newEvent: ActionReducer<newEventModel> = (state: newEventModel = initialState, action: Action) => {
// return new state
switch (action.type) {
case 'GET_EVENT':
return state;
case 'UPDATE_EVENT':
action.payload.counter = action.payload.counter + 1;
return action.payload;
case 'RESET_EVENT':
return Object.assign({}, state, {
eventOptions: [],
eventTypeId: null,
duration: 1440,
comment: '',
privacyId: 0,
isGlobal: false,
id: '',
userIds: [],
counter: 0
});
default:
return state;
}
}
I could provide a working plunkr if needed, but I need to create it first.
TLDR: How can I reset the state on @ngrx/store?
Thanks for any help provided!
Noy Levi had the right thinking in her answer to this question, which assigns initialState back into state, however, there is a way to assign initialState for each reducer automatically.
The key concept to understand is that if the value of 'state' passed into a reducer is 'undefined' (not 'null', it needs to be 'undefined') then the reducer will automatically assign into 'state' the initialState provided to the reducer when it was created. Because of this default behavior, you can create a 'metareducer' that recognizes an action, say 'logout', and then passes a state of 'undefined' into all the subsequent reducers called.
This behavior is described well in this article about redux, this article about NgRx, and also in this answer about NgRx.
The relevant code would look like this:
export function logoutClearState(reducer) {
return function (state, action) {
if (action.type === ActionTypes.LOGOUT) {
state = undefined;
}
return reducer(state, action);
};
}
@NgModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers: [logoutClearState] }),
],
declarations: [],
providers: [],
})
You can reset the state to initialState
in your reducer by using Object.assign
to copy all properties of initialState
to a new object.
export const newEvent: ActionReducer<newEventModel> = (state: newEventModel = initialState, action: Action) => {
// return new state
switch (action.type) {
// ...
case 'RESET_EVENT':
return Object.assign({}, initialState);
// ...
}
}
The reducer should be a pure function, so should not modify the arguments. Your UPDATE_EVENT
requires a little tweak:
case 'UPDATE_EVENT':
let counter = { counter: action.payload.counter + 1 };
return Object.assign({}, action.payload, counter);
The pattern to follow is Object.assign({}, source1, source2, ...)
where source1
, source2
etc contain properties to be assigned. Properties in source1
are overwritten by duplicate properties in source2
etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With