Skip to content

Commit

Permalink
chore(Example): Improve handling of book search error (#483)
Browse files Browse the repository at this point in the history
Closes #466
  • Loading branch information
peterbsmyth authored and brandonroberts committed Oct 21, 2017
1 parent 565389a commit 998b9d2
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 116 deletions.
9 changes: 8 additions & 1 deletion example-app/app/books/actions/book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Book } from '../models/book';

export const SEARCH = '[Book] Search';
export const SEARCH_COMPLETE = '[Book] Search Complete';
export const SEARCH_ERROR = '[Book] Search Error';
export const LOAD = '[Book] Load';
export const SELECT = '[Book] Select';

Expand All @@ -25,6 +26,12 @@ export class SearchComplete implements Action {
constructor(public payload: Book[]) {}
}

export class SearchError implements Action {
readonly type = SEARCH_ERROR;

constructor(public payload: string) {}
}

export class Load implements Action {
readonly type = LOAD;

Expand All @@ -41,4 +48,4 @@ export class Select implements Action {
* Export a type alias of all actions in this action group
* so that reducers can easily compose action types
*/
export type Actions = Search | SearchComplete | Load | Select;
export type Actions = Search | SearchComplete | SearchError | Load | Select;
10 changes: 9 additions & 1 deletion example-app/app/books/components/book-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,23 @@ import { Component, Output, Input, EventEmitter } from '@angular/core';
</md-input-container>
<md-spinner [class.show]="searching"></md-spinner>
</md-card-content>
<md-card-footer><span *ngIf="error">{{error}}</span></md-card-footer>
</md-card>
`,
styles: [
`
md-card-title,
md-card-content {
md-card-content,
md-card-footer {
display: flex;
justify-content: center;
}
md-card-footer {
color: #FF0000;
padding: 5px 0;
}
input {
width: 300px;
}
Expand All @@ -50,5 +57,6 @@ import { Component, Output, Input, EventEmitter } from '@angular/core';
export class BookSearchComponent {
@Input() query = '';
@Input() searching = false;
@Input() error = '';
@Output() search = new EventEmitter<string>();
}
4 changes: 3 additions & 1 deletion example-app/app/books/containers/find-book-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ import { Book } from '../models/book';
selector: 'bc-find-book-page',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<bc-book-search [query]="searchQuery$ | async" [searching]="loading$ | async" (search)="search($event)"></bc-book-search>
<bc-book-search [query]="searchQuery$ | async" [searching]="loading$ | async" [error]="error$ | async" (search)="search($event)"></bc-book-search>
<bc-book-preview-list [books]="books$ | async"></bc-book-preview-list>
`,
})
export class FindBookPageComponent {
searchQuery$: Observable<string>;
books$: Observable<Book[]>;
loading$: Observable<boolean>;
error$: Observable<string>;

constructor(private store: Store<fromBooks.State>) {
this.searchQuery$ = store.select(fromBooks.getSearchQuery).take(1);
this.books$ = store.select(fromBooks.getSearchResults);
this.loading$ = store.select(fromBooks.getSearchLoading);
this.error$ = store.select(fromBooks.getSearchError);
}

search(query: string) {
Expand Down
8 changes: 4 additions & 4 deletions example-app/app/books/effects/book.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { empty } from 'rxjs/observable/empty';
import { BookEffects, SEARCH_SCHEDULER, SEARCH_DEBOUNCE } from './book';
import { GoogleBooksService } from '../../core/services/google-books';
import { Observable } from 'rxjs/Observable';
import { Search, SearchComplete } from '../actions/book';
import { Search, SearchComplete, SearchError } from '../actions/book';
import { Book } from '../models/book';

export class TestActions extends Actions {
Expand Down Expand Up @@ -62,10 +62,10 @@ describe('BookEffects', () => {
expect(effects.search$).toBeObservable(expected);
});

it('should return a new book.SearchComplete, with an empty array, if the books service throws', () => {
it('should return a new book.SearchError if the books service throws', () => {
const action = new Search('query');
const completion = new SearchComplete([]);
const error = 'Error!';
const completion = new SearchError('Unexpected Error. Try again later.');
const error = 'Unexpected Error. Try again later.';

actions$.stream = hot('-a---', { a: action });
const response = cold('-#|', {}, error);
Expand Down
2 changes: 1 addition & 1 deletion example-app/app/books/effects/book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class BookEffects {
.searchBooks(query)
.takeUntil(nextSearch$)
.map((books: Book[]) => new book.SearchComplete(books))
.catch(() => of(new book.SearchComplete([])));
.catch(err => of(new book.SearchError(err)));
});

constructor(
Expand Down
4 changes: 4 additions & 0 deletions example-app/app/books/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export const getSearchLoading = createSelector(
getSearchState,
fromSearch.getLoading
);
export const getSearchError = createSelector(
getSearchState,
fromSearch.getError
);

/**
* Some selector functions create joins across parts of state. This selector
Expand Down
17 changes: 16 additions & 1 deletion example-app/app/books/reducers/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import * as book from '../actions/book';
export interface State {
ids: string[];
loading: boolean;
error: string;
query: string;
}

const initialState: State = {
ids: [],
loading: false,
error: '',
query: '',
};

Expand All @@ -21,25 +23,36 @@ export function reducer(state = initialState, action: book.Actions): State {
return {
ids: [],
loading: false,
error: '',
query,
};
}

return {
...state,
query,
loading: true,
error: '',
query,
};
}

case book.SEARCH_COMPLETE: {
return {
ids: action.payload.map(book => book.id),
loading: false,
error: '',
query: state.query,
};
}

case book.SEARCH_ERROR: {
return {
...state,
loading: false,
error: action.payload,
};
}

default: {
return state;
}
Expand All @@ -51,3 +64,5 @@ export const getIds = (state: State) => state.ids;
export const getQuery = (state: State) => state.query;

export const getLoading = (state: State) => state.loading;

export const getError = (state: State) => state.error;
Loading

0 comments on commit 998b9d2

Please sign in to comment.