Refactor stores to use generic store (CC-61)

pull/46/head
HardiReady 2018-10-07 17:46:29 +02:00
parent 421d15d19a
commit 8e46b6c6f1
19 changed files with 35 additions and 329 deletions

View File

@ -1,7 +1,6 @@
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {AdminComponent} from './admin.component'; import {AdminComponent} from './admin.component';
import {SharedModule} from '../shared.module'; import {SharedModule} from '../shared.module';
import {AppUserStore} from '../services/stores/app-user.store';
import {AppUserService} from '../services/app-user-service/app-user.service'; import {AppUserService} from '../services/app-user-service/app-user.service';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {RouterModule} from '@angular/router'; import {RouterModule} from '@angular/router';
@ -9,7 +8,7 @@ import {RouterModule} from '@angular/router';
@NgModule({ @NgModule({
declarations: [AdminComponent], declarations: [AdminComponent],
imports: [CommonModule, SharedModule, RouterModule.forChild([{path: '', component: AdminComponent}])], imports: [CommonModule, SharedModule, RouterModule.forChild([{path: '', component: AdminComponent}])],
providers: [AppUserStore, AppUserService] providers: [AppUserService]
}) })
export class AdminModule { export class AdminModule {
} }

View File

@ -5,10 +5,7 @@ import {AppComponent} from './app.component';
import {LoginService} from './services/app-user-service/login-service'; import {LoginService} from './services/app-user-service/login-service';
import {appRouting, routingComponents, routingProviders} from './app.routing'; import {appRouting, routingComponents, routingProviders} from './app.routing';
import {SquadService} from './services/army-management/squad.service'; import {SquadService} from './services/army-management/squad.service';
import {SquadStore} from './services/stores/squad.store';
import {DecorationStore} from './services/stores/decoration.store';
import {DecorationService} from './services/army-management/decoration.service'; import {DecorationService} from './services/army-management/decoration.service';
import {RankStore} from './services/stores/rank.store';
import {RankService} from './services/army-management/rank.service'; import {RankService} from './services/army-management/rank.service';
import {AppConfig} from './app.config'; import {AppConfig} from './app.config';
import {LoginGuardAdmin, LoginGuardHL, LoginGuardSQL} from './login'; import {LoginGuardAdmin, LoginGuardHL, LoginGuardSQL} from './login';
@ -19,7 +16,6 @@ import {PromotionService} from './services/army-management/promotion.service';
import {SharedModule} from './shared.module'; import {SharedModule} from './shared.module';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {UserService} from './services/army-management/user.service'; import {UserService} from './services/army-management/user.service';
import {UserStore} from './services/stores/user.store';
import {CookieService} from 'ngx-cookie-service'; import {CookieService} from 'ngx-cookie-service';
import {SnackBarService} from './services/user-interface/snack-bar/snack-bar.service'; import {SnackBarService} from './services/user-interface/snack-bar/snack-bar.service';
import {HttpClientModule} from '@angular/common/http'; import {HttpClientModule} from '@angular/common/http';
@ -48,14 +44,10 @@ import {SettingsService} from './services/settings.service';
LoginGuardHL, LoginGuardHL,
LoginGuardAdmin, LoginGuardAdmin,
UserService, UserService,
UserStore,
ArmyService, ArmyService,
SquadService, SquadService,
SquadStore,
DecorationService, DecorationService,
DecorationStore,
RankService, RankService,
RankStore,
AwardingService, AwardingService,
PromotionService, PromotionService,
AppConfig, AppConfig,

View File

@ -1,18 +1,11 @@
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {SharedModule} from '../shared.module'; import {SharedModule} from '../shared.module';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {RankService} from '../services/army-management/rank.service';
import {DecorationService} from '../services/army-management/decoration.service';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpClient} from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import {TranslateHttpLoader} from '@ngx-translate/http-loader'; import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {manageRouterModule, manageRoutingComponents} from './manage.routing'; import {manageRouterModule, manageRoutingComponents} from './manage.routing';
import {InfiniteScrollModule} from 'ngx-infinite-scroll'; import {InfiniteScrollModule} from 'ngx-infinite-scroll';
import {DecorationStore} from '../services/stores/decoration.store';
import {RankStore} from '../services/stores/rank.store';
import {SquadStore} from '../services/stores/squad.store';
import {SquadService} from '../services/army-management/squad.service';
export function createTranslateLoader(http: HttpClient) { export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/manage/', '.json'); return new TranslateHttpLoader(http, './assets/i18n/manage/', '.json');
@ -35,15 +28,6 @@ export function createTranslateLoader(http: HttpClient) {
}, },
isolate: true isolate: true
}) })
],
providers: [
RankStore,
RankService,
DecorationStore,
DecorationService,
SquadStore,
SquadService
] ]
}) })
export class ManageModule { export class ManageModule {

View File

@ -5,7 +5,7 @@ import {ActivatedRoute, Router} from '@angular/router';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {UserService} from '../../../services/army-management/user.service'; import {UserService} from '../../../services/army-management/user.service';
import {User} from '../../../models/model-interfaces'; import {User} from '../../../models/model-interfaces';
import {ADD, LOAD} from '../../../services/stores/user.store'; import {ADD, LOAD} from '../../../services/stores/generic-store';
import {Fraction} from '../../../utils/fraction.enum'; import {Fraction} from '../../../utils/fraction.enum';
import {MatButtonToggleGroup} from '@angular/material'; import {MatButtonToggleGroup} from '@angular/material';
import {UIHelpers} from '../../../utils/global.helpers'; import {UIHelpers} from '../../../utils/global.helpers';

View File

@ -1,20 +1,20 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {AppUser} from '../../models/model-interfaces'; import {AppUser} from '../../models/model-interfaces';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {EDIT, LOAD, REMOVE} from '../stores/user.store'; import {EDIT, LOAD, REMOVE, Store} from '../stores/generic-store';
import {AppConfig} from '../../app.config'; import {AppConfig} from '../../app.config';
import {HttpClient} from '../http-client'; import {HttpClient} from '../http-client';
import {AppUserStore} from '../stores/app-user.store';
@Injectable() @Injectable()
export class AppUserService { export class AppUserService {
users$: Observable<AppUser[]>; users$: Observable<AppUser[]>;
private appUserStore = new Store<AppUser>();
constructor(private http: HttpClient, constructor(private http: HttpClient,
private appUserStore: AppUserStore,
private config: AppConfig) { private config: AppConfig) {
this.users$ = appUserStore.items$; this.users$ = this.appUserStore.items$;
} }
getUsers() { getUsers() {
@ -43,5 +43,4 @@ export class AppUserService {
this.appUserStore.dispatch({type: REMOVE, data: user}); this.appUserStore.dispatch({type: REMOVE, data: user});
}); });
} }
} }

View File

@ -2,22 +2,21 @@ import {Injectable} from '@angular/core';
import {Decoration} from '../../models/model-interfaces'; import {Decoration} from '../../models/model-interfaces';
import {RequestMethod, RequestOptions, URLSearchParams} from '@angular/http'; import {RequestMethod, RequestOptions, URLSearchParams} from '@angular/http';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {ADD, DecorationStore, EDIT, LOAD, REMOVE} from '../stores/decoration.store'; import {ADD, EDIT, LOAD, REMOVE, Store} from '../stores/generic-store';
import {AppConfig} from '../../app.config'; import {AppConfig} from '../../app.config';
import {HttpClient} from '../http-client'; import {HttpClient} from '../http-client';
@Injectable() @Injectable()
export class DecorationService { export class DecorationService {
decorations$: Observable<Decoration[]>; decorations$: Observable<Decoration[]>;
constructor(private http: HttpClient, decorationStore = new Store<Decoration>();
private decorationStore: DecorationStore,
private config: AppConfig) {
this.decorations$ = decorationStore.items$;
}
constructor(private http: HttpClient,
private config: AppConfig) {
this.decorations$ = this.decorationStore.items$;
}
findDecorations(query = '', fractionFilter?) { findDecorations(query = '', fractionFilter?) {
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
@ -72,7 +71,6 @@ export class DecorationService {
body = decoration; body = decoration;
} }
const options = new RequestOptions({ const options = new RequestOptions({
body: body, body: body,
method: requestMethod, method: requestMethod,
@ -86,7 +84,6 @@ export class DecorationService {
}); });
} }
deleteDecoration(decoration: Decoration) { deleteDecoration(decoration: Decoration) {
return this.http.delete(this.config.apiDecorationPath + decoration._id) return this.http.delete(this.config.apiDecorationPath + decoration._id)
.do(res => { .do(res => {
@ -94,4 +91,3 @@ export class DecorationService {
}); });
} }
} }

View File

@ -1,24 +1,22 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Decoration, Rank} from '../../models/model-interfaces'; import {Rank} from '../../models/model-interfaces';
import {RequestMethod, RequestOptions, URLSearchParams} from '@angular/http'; import {RequestMethod, RequestOptions, URLSearchParams} from '@angular/http';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {LOAD} from '../stores/decoration.store'; import {ADD, EDIT, LOAD, REMOVE, Store} from '../stores/generic-store';
import {ADD, EDIT, RankStore, REMOVE} from '../stores/rank.store';
import {AppConfig} from '../../app.config'; import {AppConfig} from '../../app.config';
import {HttpClient} from '../http-client'; import {HttpClient} from '../http-client';
@Injectable() @Injectable()
export class RankService { export class RankService {
ranks$: Observable<Rank[]>; ranks$: Observable<Rank[]>;
constructor(private http: HttpClient, rankStore = new Store<Rank>();
private rankStore: RankStore,
private config: AppConfig) {
this.ranks$ = rankStore.items$;
}
constructor(private http: HttpClient,
private config: AppConfig) {
this.ranks$ = this.rankStore.items$;
}
findRanks(query = '', fractionFilter?) { findRanks(query = '', fractionFilter?) {
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
@ -90,55 +88,10 @@ export class RankService {
}); });
} }
/**
* send PATCH request to update db entry
*
* @param rank - Rank object with data to update, must contain _id
* @returns {Observable<T>}
*/
updateRank(rank: Rank) {
const options = new RequestOptions({
body: rank,
method: RequestMethod.Patch
});
return this.http.request(this.config.apiRankPath + rank._id, options)
.map(res => res.json())
.do(savedRank => {
const action = {type: EDIT, data: savedRank};
this.rankStore.dispatch(action);
});
}
/**
* sends PATCH with multiform data to
* update rank graphic with newly provided file
*
* @param rankID - id of rank to be updated
* @param imageFile - new image file to upload
*/
updateRankGraphic(rankId: string, imageFile: File) {
const formData: FormData = new FormData();
formData.append('_id', rankId);
formData.append('image', imageFile, imageFile.name);
// provide token as query value, because there is no actual body
// and x-access-token is ignored in multipart request
return this.http.patch(this.config.apiRankPath + rankId, formData)
.map(res => res.json())
.do(savedDecoration => {
const action = {type: EDIT, data: savedDecoration};
this.rankStore.dispatch(action);
});
}
deleteRank(rank: Rank) { deleteRank(rank: Rank) {
return this.http.delete(this.config.apiRankPath + rank._id) return this.http.delete(this.config.apiRankPath + rank._id)
.do(res => { .do(res => {
this.rankStore.dispatch({type: REMOVE, data: rank}); this.rankStore.dispatch({type: REMOVE, data: rank});
}); });
} }
} }

View File

@ -2,8 +2,7 @@ import {Injectable} from '@angular/core';
import {Squad} from '../../models/model-interfaces'; import {Squad} from '../../models/model-interfaces';
import {RequestMethod, RequestOptions, URLSearchParams} from '@angular/http'; import {RequestMethod, RequestOptions, URLSearchParams} from '@angular/http';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {ADD, EDIT, LOAD, REMOVE, Store} from '../stores/generic-store';
import {ADD, EDIT, LOAD, REMOVE, SquadStore} from '../stores/squad.store';
import {AppConfig} from '../../app.config'; import {AppConfig} from '../../app.config';
import {HttpClient} from '../http-client'; import {HttpClient} from '../http-client';
@ -12,10 +11,11 @@ export class SquadService {
squads$: Observable<Squad[]>; squads$: Observable<Squad[]>;
squadStore = new Store<Squad>();
constructor(private http: HttpClient, constructor(private http: HttpClient,
private squadStore: SquadStore,
private config: AppConfig) { private config: AppConfig) {
this.squads$ = squadStore.items$; this.squads$ = this.squadStore.items$;
} }
findSquads(query = '', fractionFilter = '') { findSquads(query = '', fractionFilter = '') {
@ -33,13 +33,11 @@ export class SquadService {
return this.squads$; return this.squads$;
} }
getSquad(id: number | string): Observable<Squad> { getSquad(id: number | string): Observable<Squad> {
return this.http.get(this.config.apiSquadPath + id) return this.http.get(this.config.apiSquadPath + id)
.map(res => res.json()); .map(res => res.json());
} }
/** /**
* For creating new data with POST or * For creating new data with POST or
* update existing with patch PATCH * update existing with patch PATCH
@ -90,6 +88,4 @@ export class SquadService {
this.squadStore.dispatch({type: REMOVE, data: squad}); this.squadStore.dispatch({type: REMOVE, data: squad});
}); });
} }
} }

View File

@ -2,7 +2,7 @@ import {Injectable} from '@angular/core';
import {User} from '../../models/model-interfaces'; import {User} from '../../models/model-interfaces';
import {URLSearchParams} from '@angular/http'; import {URLSearchParams} from '@angular/http';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {ADD, EDIT, LOAD, REMOVE, UserStore} from '../stores/user.store'; import {ADD, EDIT, LOAD, REMOVE, Store} from '../stores/generic-store';
import {AppConfig} from '../../app.config'; import {AppConfig} from '../../app.config';
import {HttpClient} from '../http-client'; import {HttpClient} from '../http-client';
@ -11,12 +11,13 @@ export class UserService {
users$: Observable<User[]>; users$: Observable<User[]>;
userStore = new Store<User>();
totalCount = 0; totalCount = 0;
constructor(private http: HttpClient, constructor(private http: HttpClient,
private userStore: UserStore,
private config: AppConfig) { private config: AppConfig) {
this.users$ = userStore.items$; this.users$ = this.userStore.items$;
} }
findUsers(filter, limit?, offset?, action = LOAD) { findUsers(filter, limit?, offset?, action = LOAD) {
@ -81,6 +82,4 @@ export class UserService {
this.userStore.dispatch({type: REMOVE, data: user}); this.userStore.dispatch({type: REMOVE, data: user});
}); });
} }
} }

View File

@ -14,5 +14,4 @@ export class ArmyService {
return this.http.get(this.config.apiOverviewPath) return this.http.get(this.config.apiOverviewPath)
.map(res => res.json()); .map(res => res.json());
} }
} }

View File

@ -25,7 +25,6 @@ export class HttpClient {
this.router.navigate(['/login']); this.router.navigate(['/login']);
} }
} }
} }
get(url, searchParams?) { get(url, searchParams?) {
@ -73,5 +72,4 @@ export class HttpClient {
return this.patch(requestUrl, options.body); return this.patch(requestUrl, options.body);
} }
} }
} }

View File

@ -42,4 +42,3 @@ export class CampaignService {
.map(res => res.json()); .map(res => res.json());
} }
} }

View File

@ -1,41 +0,0 @@
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {AppUser} from '../../models/model-interfaces';
export const LOAD = 'LOAD';
export const ADD = 'ADD';
export const EDIT = 'EDIT';
export const REMOVE = 'REMOVE';
export class AppUserStore {
private appUsers: AppUser[] = [];
items$ = new BehaviorSubject<AppUser[]>([]);
dispatch(action) {
this.appUsers = this._reduce(this.appUsers, action);
this.items$.next(this.appUsers);
}
_reduce(users: AppUser[], action) {
switch (action.type) {
case LOAD:
return [...action.data];
case ADD:
return [...users, action.data];
case EDIT:
return users.map(user => {
const editedUser = action.data;
if (user._id !== editedUser._id) {
return user;
}
return editedUser;
});
case REMOVE:
return users.filter(user => user._id !== action.data._id);
default:
return users;
}
}
}

View File

@ -1,41 +0,0 @@
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Decoration} from '../../models/model-interfaces';
export const LOAD = 'LOAD';
export const ADD = 'ADD';
export const EDIT = 'EDIT';
export const REMOVE = 'REMOVE';
export class DecorationStore {
private decorations: Decoration[] = [];
items$ = new BehaviorSubject<Decoration[]>([]);
dispatch(action) {
this.decorations = this._reduce(this.decorations, action);
this.items$.next(this.decorations);
}
_reduce(decorations: Decoration[], action) {
switch (action.type) {
case LOAD:
return [...action.data];
case ADD:
return [...decorations, action.data];
case EDIT:
return decorations.map(decoration => {
const editedDecoration = action.data;
if (decoration._id !== editedDecoration._id) {
return decoration;
}
return editedDecoration;
});
case REMOVE:
return decorations.filter(decoration => decoration._id !== action.data._id);
default:
return decorations;
}
}
}

View File

@ -9,7 +9,7 @@ export const REMOVE = 'REMOVE';
type Id = string | number; type Id = string | number;
interface Identifiable { interface Identifiable {
id?: Id; _id?: Id;
} }
export class Store<T extends Identifiable> { export class Store<T extends Identifiable> {
@ -28,15 +28,15 @@ export class Store<T extends Identifiable> {
case ADD: case ADD:
return [...items, action.data]; return [...items, action.data];
case EDIT: case EDIT:
return items.map(task => { return items.map(item => {
const editedTask = action.data; const editedItem = action.data;
if (task.id !== editedTask.id) { if (item._id !== editedItem._id) {
return task; return item;
} }
return editedTask; return editedItem;
}); });
case REMOVE: case REMOVE:
return items.filter(task => task.id !== action.data.id); return items.filter(item => item._id !== action.data._id);
default: default:
return items; return items;
} }

View File

@ -1,41 +0,0 @@
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Rank} from '../../models/model-interfaces';
export const LOAD = 'LOAD';
export const ADD = 'ADD';
export const EDIT = 'EDIT';
export const REMOVE = 'REMOVE';
export class RankStore {
private ranks: Rank[] = [];
items$ = new BehaviorSubject<Rank[]>([]);
dispatch(action) {
this.ranks = this._reduce(this.ranks, action);
this.items$.next(this.ranks);
}
_reduce(ranks: Rank[], action) {
switch (action.type) {
case LOAD:
return [...action.data];
case ADD:
return [...ranks, action.data];
case EDIT:
return ranks.map(decoration => {
const editedRank = action.data;
if (decoration._id !== editedRank._id) {
return decoration;
}
return editedRank;
});
case REMOVE:
return ranks.filter(decoration => decoration._id !== action.data._id);
default:
return ranks;
}
}
}

View File

@ -1,41 +0,0 @@
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Squad} from '../../models/model-interfaces';
export const LOAD = 'LOAD';
export const ADD = 'ADD';
export const EDIT = 'EDIT';
export const REMOVE = 'REMOVE';
export class SquadStore {
private squads: Squad[] = [];
items$ = new BehaviorSubject<Squad[]>([]);
dispatch(action) {
this.squads = this._reduce(this.squads, action);
this.items$.next(this.squads);
}
_reduce(squads: Squad[], action) {
switch (action.type) {
case LOAD:
return [...action.data];
case ADD:
return [...squads, action.data];
case EDIT:
return squads.map(squad => {
const editedSquad = action.data;
if (squad._id !== editedSquad._id) {
return squad;
}
return editedSquad;
});
case REMOVE:
return squads.filter(squad => squad._id !== action.data._id);
default:
return squads;
}
}
}

View File

@ -1,44 +0,0 @@
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {User} from '../../models/model-interfaces';
export const LOAD = 'LOAD';
export const ADD = 'ADD';
export const EDIT = 'EDIT';
export const REMOVE = 'REMOVE';
export class UserStore {
private users: User[] = [];
items$ = new BehaviorSubject<User[]>([]);
dispatch(action) {
this.users = this._reduce(this.users, action);
this.items$.next(this.users);
}
_reduce(users: User[], action) {
switch (action.type) {
case LOAD:
return [...action.data];
case ADD:
if (action.data instanceof Array) {
return users.concat(action.data);
}
return [...users, action.data];
case EDIT:
return users.map(user => {
const editedUser = action.data;
if (user._id !== editedUser._id) {
return user;
}
return editedUser;
});
case REMOVE:
return users.filter(user => user._id !== action.data._id);
default:
return users;
}
}
}