Angular2 using http.get with a distinct - http

I am trying to learn more about the Http.Get. I am using a json file to mock the HTTP call. After the call I want to limit the rows to unique states. I know there is an Rxjs command called distinct (help on Rxjs distinct). However I do not understand the syntax for the distinct. When I run this code as is I get an array of states. However when I add the distinct it still has duplicated states like Texas.
public getStates(): Observable<IState[]> {
return this._http.get(this.stateUrl)
.map((res: Response) => <IState[]>res.json())
// distinct by state name
.distinct((x) => return x.state)
;
}
Here is the interface for IState
export interface IState {
id: number;
state: string;
city: string;
name: string;
}
Trying to only get rows with a unique state.
I have repo for the code on this Github project

You're confusing a bit the meaning of the distinct in this situation!
This distinct is meant to filter the response from your Observable, not its content! Here you're trying to "distinct" (filter) your json object, but the distinct will filter the responses (multiple) "resolved" by the Observable. So, if the observable "resolves" the same response multiple times, you'll receive only once, because of your .distinct.
This means, if the 1st time the Observable returns you back one json (e.g [{0: a, 1:b, 2:c}]) and the second time he returns the same json reponse, then your "subscribers" will not receive it twice, because the .distinct will "filter" it and as a result it will not be fired!
more details here: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/distinct.md
So, what you're looking for, is how to "filter" duplicated results within your json.
The answer is simple: use a loop to check its properties/values or some library like underscore which might have this method already implemented.

The IState is an object. You have to define what exactly makes them distinct from each other.You can look at the documentation, there is a way to define what makes an object distinct
For example:
/* Without key selector */
var source = Rx.Observable.of(42, 24, 42, 24)
.distinct();
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
// => Next: 42
// => Next: 24
// => Completed
/* With key selector */
var source = Rx.Observable.of({value: 42}, {value: 24}, {value: 42}, {value: 24})
.distinct(function (x) { return x.value; });
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
// => Next: { value: 42 }
// => Next: { value: 24 }
// => Completed

The problem is that when you're calling .distinct((x) => return x.state) the x variable is the entire array from https://github.com/ATXGearHead12/distinct/blob/master/src/api/state.json. Then property x.state doesn't exists.
So you need to call distinct on each item in the array not on the array itself.
For example like the following:
this._http.get(this.stateUrl)
.map((res: Response) => <IState[]>res.json())
.concatMap(arr =>
Observable.from(arr).distinct((x) => return x.state)
)
.toArray(); // collect all items into an array

Related

Search & filter function mutates the state and doesn't return initial state when items have been cleared

I've implemented a search function which will search and return results from my redux store.
However the result get returned and when I clear the text field the initial state should be return. I end up getting an empty store when I've cleared or entered the wrong keywords.
here is my search & filter function
filter: (state, { payload }) => {
const itemsToFilter = state.filter((item) => {
let itemLowerCase = item.item_description.toLowerCase();
let searchItemToLowerCase = payload.item_description.toLowerCase();
return itemLowerCase.indexOf(searchItemToLowerCase) > -1;
});
if (itemsToFilter) {
return itemsToFilter;
}
return itemInitialState; //initial state of items is not returned
}
and I used useDispatch from react-redux
I'm assuming that in the case where 'you clear the text field' that the result is an event where payload.item_description is ''. In that case, Array.filter() won't match anything, but it will still return an empty array (not null). So, your check of itemsToFilter returns true and your new state is empty. Just check the length of the returned array instead.
filter: (state, { payload }) => {
const itemsToFilter = state.filter((item) => {
...
});
if (itemsToFilter.length === 0) {
return itemsToFilter;
}
return itemInitialState;
}

Redux Toolkit - assign entire array to state

How can I assign an entire array to my intialState object using RTK?
Doing state = payload or state = [...state, ...payload] doesn't update anything.
Example:
const slice = createSlice({
name: 'usersLikedPosts',
initialState: [],
reducers: {
getUsersLikedPosts: (state, { payload }) => {
if (payload.length > 0) {
state = payload
}
},
},
})
payload looks like this:
[
0: {
uid: '1',
title: 'testpost'
}
]
update
Doing this works but I don't know if this is a correct approach. Can anyone comment?
payload.forEach((item) => state.push(item))
immer can only observe modifications to the object that was initially passed into your function through the state argument. It is not possible to observe from outside the function if that variable was reassigned, as it only exists in the scope within the function.
You can, however, just return a new value instead of modifying the old one, if you like that better. (And in this case, it is probably a bit more performant than doing a bunch of .push calls)
So
return [...state, ...payload]
should do what you want.

RxJs Compose a list of Observables and then combine (Firebase)

I'm attempting to combine a list of observables, but haven't had luck with zip or other maps.
What I want to do is get a list of Genres for a given Artist Id. I'm using firebase, so it's a nosql database. I have a 'genresPerArtist' list that is a list of Artist keys, and each Artist key has a list of Genre keys.
Structure:
{
genresPerArtist: {
artist1: {
genre1: true,
genre2: true,
genre3: true
}
},
genres: {
genre1: {
name: 'Alternative'
},
genre2: {
name: 'Jazz'
}
}
}
Here's the start of the function that I have:
getGenresByArtistId(artistId: string): Observable<any> {
return this.db.list(`/genresPerArtist/${artistId}`)
.flatMap((genres: IDictionary[]) => {
return genres.map(genre => this.db.list(`/genres/${genre.$key}`));
// return Observable.zip(obvs);
});
I'm not really sure what do do with the list that I get back from genresPerArtist. What I want to do, is take each genre.key (genre1, genre2, genre3), and use each one to fetch the value from 'genres'. I thought I could return a .zip, and then map over that, but I was not getting a response from zip.
#cartant, thanks, that included what I needed, forkJoin.
// Compose an observable based on the genresPerArtist:
return this.db.list(`/genresPerArtist/${artistId}`)
// Each time the genresPerArtist emits, switch to unsubscribe/ignore any pending user queries:
.switchMap(genres => {
// Map the genres to the array of observables that are to be joined.
let genreObservables = genres.map(genre => this.db.object(`/genres/${genre.$key}`).first());
return Observable.forkJoin(...genreObservables);
});
You could try with the following, asumming that the return of genresPerArtist returns a Map<string, boolean>:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/mergeMap';
import { zip } from 'rxjs/observable/zip';
getGenresByArtistId(artistId: string): Observable<Genre[]>{
return this.db.list(`/genresPerArtist/${artistId}`)
.mergeMap((genres: Map<string, boolean>) =>
zip(Array.from(genres.keys()).map(genre => this.db.list(`/genres/${genre}`))))
}
EDIT: Another option would be
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/toArray';
getGenresByArtistId(artistId: string): Observable<Genre[]>{
return this.db.list(`/genresPerArtist/${artistId}`)
.mergeMap((genres: Map<string, boolean>) => Array.from(genres.keys())
.mergeMap(genres=> genres)
.mergeMap(genre => this.db.list(`/genres/${genre}`))
.toArray();
}
Basically Im flattening the results into a single array manually. I took the long way to make it easier to follow

HTTP: Angular 2+TS How to use Observables in HTTP

I found an example from angular.io. This example is very similar to my app, with same kind of methods. This example is using Promises, but I'm using Observables. If I use this example as a reference, I have every method working in my app, except the getHero method in the service, and the ngOnInit in the HeroDetailComponent. So I'm wondering if someone can help and convert this method to an observable, because I'm having trouble with the syntax. Here is the codes I need converted to Observable and the plunker
//HeroService
getHero(id: number) { // my id is String
return this.getHeroes()
.then(heroes => heroes.filter(hero => hero.id === id)[0]);
}
//HeroDetailComponent
ngOnInit() {
if (this.routeParams.get('id') !== null) {
let id = +this.routeParams.get('id');
this.navigated = true;
this.heroService.getHero(id)
.then(hero => this.hero = hero);
} else {
this.navigated = false;
this.hero = new Hero();
}
}
So I want something like this:
//HeroService
public getHero(id: string) {
return this.getHeroes()
.subscribe(heroes => this.heroes.filter(hero => heroes.id === id)[0]); //BTW, what does this [0] mean??
}
EDIT: I had to actually retrieve the list directly, it didn't work with return this.heroes as suggested in answers below. Working example:
public getById(id: string) {
//return this.getHeroes() <---- didn't work
return this.http.get('someUrl') // WORKS!
.map(heroes => this.heroes.filter(hero => hero.id === id)[0]);
}
Now I'm still having trouble with my ngOnit, and I can't really understand why!
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getById(id)
//console.log("retrieved id: ",id ) <----- gives correct id!
.subscribe(hero => this.hero = hero);
//console.log("hero: ", this.hero); <----- gives undefined!
}
EDIT2, still getting undefined when trying to move to the detail page :( I think you had one bracket to much in your answer, tried to look and get the correct places for the brackets?
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getById(id)
.subscribe(heroes => {
// this code is executed when the response from the server arrives
this.hero = hero
});
// code here is executed before code from the server arrives
// even though it is written below
}
If you call subscribe() on an Observable a Subscription is returned. You can't call subscribe() on a subscription.
Instead use just an operator (map()) and use subscribe() on the call site:
public getHero(id: string) {
return this.getHeroes()
.map(heroes => this.heroes.filter(hero => heroes.id === id)[0]);
}
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
In contrary to subscribe(), map() also operates on an Observable but also returns an Observable.
[0] means to just take the first item of the filtered heroes.
update
ngOnInit(){
let id = this._routeParams.get('id');
this._searchService.getById(id)
.subscribe(searchCase => {
// this code is executed when the response from the server arrives
this.searchCase = searchCase;
console.log("id: ", this.searchCase);
});
// code here is executed before code from the server arrives
// event though it is written below
}
This code is a function
searchCase => {
// this code is executed when the response from the server arrives
this.searchCase = searchCase);
console.log("id: ", this.searchCase);
}
that is passed to subscribe() and the Observable calls this function when it has new data for the subscriber. Therefore this code is not executed immediately but only when the observable emits new data.
Code that comes after subscribe() is executed immediately and therefore before above function and therefore this.searchCase does not yet have a value.
This is a way you can do it:
//HeroService
public getHero(id: string) {
return this.getHeroes()
.map(heroes => this.heroes.filter(hero => heroes.id === id)[0]);
}
//HeroDetailComponent
ngOnInit(){
let id = this._routeParams.get('id');
this.heroService.getHero(id)
.subscribe(hero => {
// your code here
});
}
The [0] is an array accessor. You're selecting the first element on array index 0 with it. You need this, because Array.filter() returns a new array with the filtered values, but you only want one hero.

React-redux project - chained dependent async calls not working with redux-promise middleware?

I'm new to using redux, and I'm trying to set up redux-promise as middleware. I have this case I can't seem to get to work (things work for me when I'm just trying to do one async call without chaining)
Say I have two API calls:
1) getItem(someId) -> {attr1: something, attr2: something, tagIds: [...]}
2) getTags() -> [{someTagObject1}, {someTagObject2}]
I need to call the first one, and get an item, then get all the tags, and then return an object that contains both the item and the tags relating to that item.
Right now, my action creator is like this:
export function fetchTagsForItem(id = null, params = new Map()) {
return {
type: FETCH_ITEM_INFO,
payload: getItem(...) // some axios call
.then(item => getTags() // gets all tags
.then(tags => toItemDetails(tags.data, item.data)))
}
}
I have a console.log in toItemDetails, and I can see that when the calls are completed, we eventually get into toItemDetails and result in the right information. However, it looks like we're getting to the reducer before the calls are completed, and I'm just getting an undefined payload from the reducer (and it doesn't try again). The reducer is just trying to return action.payload for this case.
I know the chained calls aren't great, but I'd at least like to see it working. Is this something that can be done with just redux-promise? If not, any examples of how to get this functioning would be greatly appreciated!
I filled in your missing code with placeholder functions and it worked for me - my payload ended up containing a promise which resolved to the return value of toItemDetails. So maybe it's something in the code you haven't included here.
function getItem(id) {
return Promise.resolve({
attr1: 'hello',
data: 'data inside item',
tagIds: [1, 3, 5]
});
}
function getTags(tagIds) {
return Promise.resolve({ data: 'abc' });
}
function toItemDetails(tagData, itemData) {
return { itemDetails: { tagData, itemData } };
}
function fetchTagsForItem(id = null) {
let itemFromAxios;
return {
type: 'FETCH_ITEM_INFO',
payload: getItem(id)
.then(item => {
itemFromAxios = item;
return getTags(item.tagIds);
})
.then(tags => toItemDetails(tags.data, itemFromAxios.data))
};
}
const action = fetchTagsForItem(1);
action.payload.then(result => {
console.log(`result: ${JSON.stringify(result)}`);
});
Output:
result: {"itemDetails":{"tagData":"abc","itemData":"data inside item"}}
In order to access item in the second step, you'll need to store it in a variable that is declared in the function scope of fetchTagsForItem, because the two .thens are essentially siblings: both can access the enclosing scope, but the second call to .then won't have access to vars declared in the first one.
Separation of concerns
The code that creates the action you send to Redux is also making multiple Axios calls and massaging the returned data. This makes it more complicated to read and understand, and will make it harder to do things like handle errors in your Axios calls. I suggest splitting things up. One option:
Put any code that calls Axios in its own function
Set payload to the return value of that function.
Move that function, and all other funcs that call Axios, into a separate file (or set of files). That file becomes your API client.
This would look something like:
// apiclient.js
const BASE_URL = 'https://yourapiserver.com/';
const makeUrl = (relativeUrl) => BASE_URL + relativeUrl;
function getItemById(id) {
return axios.get(makeUrl(GET_ITEM_URL) + id);
}
function fetchTagsForItemWithId(id) {
...
}
// Other client calls and helper funcs here
export default {
fetchTagsForItemWithId
};
Your actions file:
// items-actions.js
import ApiClient from './api-client';
function fetchItemTags(id) {
const itemInfoPromise = ApiClient.fetchTagsForItemWithId(id);
return {
type: 'FETCH_ITEM_INFO',
payload: itemInfoPromise
};
}

Resources