Created
March 18, 2018 18:09
-
-
Save krismeister/db76922101aeaaf592bf6fed5099e323 to your computer and use it in GitHub Desktop.
NGRX Effects are complicated
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Injectable} from '@angular/core'; | |
import {Observable} from 'rxjs/Observable'; | |
import {Store} from '@ngrx/store'; | |
import {Actions, Effect} from '@ngrx/effects'; | |
import * as advancedSearchActions from './actions'; | |
import {createLogger, LOG_LEVELS} from '../../../shared/logger'; | |
import {TenantSearchService} from "../../api/tenantSearch/tenantSearch.service"; | |
import {SearchQueryParams} from "../../ng-models/search/SearchQueryParams.interface"; | |
import {ActivatedRoute, Router} from "@angular/router"; | |
import {ADVANCED_SEARCH_TYPES} from "./reducers"; | |
import {MiServiceSearchResults} from "../../models/SearchResults"; | |
const log = createLogger(LOG_LEVELS.REDUX_ADVANCED_SEARCH); | |
@Injectable() export class AdvancedSearchEffects { | |
/** | |
* depeplinkChanged listens for AS_DEEP_LINK_CHANGED, this is when the URL changes. | |
*/ | |
@Effect() | |
deepLinkChanged: Observable<advancedSearchActions.Actions> = this.actions$ | |
// only listen to this type: | |
.ofType(advancedSearchActions.ActionTypes.AS_DEEP_LINK_CHANGED) | |
// switchMap, allows getCorrectEndpoint() to return an HTTP observable, | |
// but will wait until the flattened results before moving onto the next .map call | |
.switchMap( (action: advancedSearchActions.Actions ) => this.getCorrectEndpoint(action.payload)) | |
// map modifies a flat response, here we are returning the AsDataLoadSuccess action | |
.map( (response) => { | |
// log('response is', response); | |
return new advancedSearchActions.AsDataLoadSuccess(response); | |
}); | |
/** | |
* loadMore listens for AS_LOAD_MORE, this is when the user clicks the Load More button. | |
*/ | |
@Effect() | |
loadMore: Observable<advancedSearchActions.Actions> = this.actions$ | |
// only listen to this type: | |
.ofType(advancedSearchActions.ActionTypes.AS_LOAD_MORE) | |
// mergeMap takes the observable from mergeWithState and waits for a flattened object for the next .map | |
.mergeMap( (action) => this.mergeWithState(action)) | |
// we now have both the full AdvancedSearchState and the original action | |
.map( ( {asState, action}) => { | |
//combined now has a shape with params(from current state) and action combined. | |
const newParams = {...asState.results.meta.params}; | |
const qtyToAdd = (<any>action).payload.qty; | |
newParams.from = Number(newParams.from) + Number(newParams.size); | |
newParams.size = Number(qtyToAdd); | |
return newParams; | |
}) | |
// this fixes an angular 2 bug in arrays | |
.map (query => this.convertArrayQueries(query)) | |
// here we | |
.switchMap( (newParams ) => { | |
// switchMap, allows getCorrectEndpoint() to return an HTTP observable, | |
// but will wait until the flattened results before moving onto the next .map call | |
return this.getCorrectEndpoint(newParams); | |
}) | |
.map( (response) => { | |
// map modifies a flat response, here we are returning the AsLoadMoreSuccess action | |
return new advancedSearchActions.AsLoadMoreSuccess(response); | |
}); | |
/** | |
* filterAdd - Add or remove filters | |
*/ | |
@Effect({ dispatch: false }) | |
filterAdd: Observable<advancedSearchActions.Actions> = this.actions$ | |
.ofType(advancedSearchActions.ActionTypes.AS_FILTER_UPDATE) | |
.mergeMap( (action) => this.mergeWithState(action)) | |
.map( ({asState, action}) => { | |
//combined now has a shape with params(from current state) and action combined. | |
const newParams = {...asState.results.meta.params}; | |
const filtersToAdd = (<any>action).payload; | |
//for each new filter key, we apply the value | |
for (let [filterKey, filterValue] of Object.entries(filtersToAdd)) { | |
newParams[filterKey] = filterValue; | |
} | |
return newParams; | |
}) | |
// this fixes an angular 2 bug in arrays | |
.map (query => this.convertArrayQueries(query)) | |
.map( (newParams) => { | |
this.navigate(newParams); | |
return null; | |
}); | |
/** | |
* Returns an observable which when resolved will return an new combine object | |
* @param {any} action The original advancedSearchActions.Actions | |
* @returns {Observable<any>} Resolves to an object with shape {asState, action} | |
*/ | |
mergeWithState(action: any):Observable<any> { | |
return this.store.first().map( | |
(state) => { | |
return {asState: state.advancedSearch, action} | |
} | |
); | |
} | |
/** | |
* Decides which search endpoint to hit | |
* @param newParams The new parameters to send to that endpoint | |
* @returns {Observable<MiServiceSearchResults>} | |
*/ | |
getCorrectEndpoint(newParams): Observable<MiServiceSearchResults> { | |
return this.store.first() | |
.switchMap( (state) => { | |
switch(state.advancedSearch.searchType) { | |
case ADVANCED_SEARCH_TYPES.EVENT: { | |
// this is the search for events endpoint | |
return this.tenantSearchService.searchTenantEvents(newParams as SearchQueryParams) | |
} | |
default: { | |
// this is the default | |
// MI_SERVICE is the default: | |
return this.tenantSearchService.searchTenantMiServices(newParams as SearchQueryParams); | |
} | |
} | |
} | |
); | |
} | |
/** | |
* Will update the current page URL query | |
* @param newQuery {object} The object serialize for the query. | |
*/ | |
navigate(newQuery: object): void { | |
// console.log('newParams', newQuery); | |
// remove params which should not be stored in the URL | |
['owner', 'from', 'size'].forEach(unsafeParam => { | |
delete newQuery[unsafeParam]; | |
}); | |
this.router.navigate([], { relativeTo: this.route, queryParams: newQuery}); | |
} | |
/** | |
* Fixes and angular 4 bug where arrays are not serialized correctly | |
* @param {Object} query | |
* @returns {Object} | |
*/ | |
convertArrayQueries(query: object): object { | |
const newQuery = {}; | |
for (let [key, value] of Object.entries(query)) { | |
if (Array.isArray(value)) { | |
newQuery[key+'[]'] = value; | |
} else { | |
newQuery[key] = value; | |
} | |
} | |
return newQuery | |
} | |
constructor( | |
private actions$: Actions, | |
private tenantSearchService: TenantSearchService, | |
private router: Router, | |
private route: ActivatedRoute, | |
private store: Store<any> | |
) {} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// How to use the ASDeeplinkChanged Effect | |
this.activatedRoute.queryParams | |
.subscribe( (queryParams) => { | |
this.store.dispatch(new ASActions.AsDeeplinkChanged(...queryParams)); | |
this.loaded = true; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The purpose of this effects file is to interface with a search backed, the API calls are structured similarly to this:
?time_range=30_DAY¬ification_event_type%5B%5D=EVENT_TYPE_AUTO_RENEW&product_category%5B%5D=Support&mrc_min=50&mrc_max=90