Reputation: 463
@Component({
selector: 'app-user',
styleUrls: ['./user.component.css'],
template: `
<button (click)="clickSearch('some keyword')">Search</button>
<div *ngIf="books$ | async as books">
<h5 *ngIf="hasSearched">No. of books: {{ books.length }}</h5>
<p *ngIf="hasSearched && !books.length">No books found</p>
<p *ngIf="!hasSearched">Please enter a search keyword to start searching for books.</p>
<h2 *ngFor="let book of books">{{ book.title }}</h2>
</div>
`
})
export class UserComponent {
constructor(private store: Store) {}
hasSearched: boolean = false;
books$ = this.store.select(BooksSelector.getBooks)
.pipe(
tap(data => console.log(data))
);
clickSearch(keyword: string) {
this.hasSearched = true;
this.store.dispatch(BooksActions.loadBooksFromApi(
{ q: keyword }
));
}
}
Let's say I have a simplified Angular component code that looks like above. The flow:
When we first arrive into this page, the store.select() statement immediately returns the initial state value from the store which is []
. This will cause the template to show the "No books found" and "No. of books=0" messages, which I do not want. So to prevent it, I added the hasSearched
boolean property and display the "Please enter..." message instead.
When the Search button is clicked, the hasSearched
property becomes true
and while the API is retrieving the data, the template will show the "No books found" and "No. of books=0" messages briefly. I do not want it to behave this way. I would like the messages to wait (by being hidden) until the API call is completed.
When the API call is completed, the list of books appear normally in the template and the messages appear correctly.
After the user has searched something and get some results, then leaves the page and returns to it, the store.select() statement will return the previous store value. I do not want this. I simply want this page to start fresh with no list of books and only the message "Please enter a search keyword to start searching for books.".
My questions are for points number 2 and 4:
For point number 2, should I add another boolean property to check for loading state? Seems like too many *ngIf flags. Is there a more elegant way to handle this via NgRx selectors?
For point number 4, how do I achieve this? Is it by pushing an empty []
to the store during ngOnDestroy()
to force clear the Books state?
Upvotes: 2
Views: 719
Reputation: 2184
For point number 2
I would suggest adding another boolean hasSearched: boolean
in your reducer, and remove hasSearched
from the component.
Subscribe to hasSearched
using a selector in your component.
When the user enters the component first time it should be undefined
. From that, you can display your message
<p *ngIf="(hasSearched | async) === undefined">
Please enter a search keyword to start searching for books.
</p>
When the user clicks on the button you will dispatch your action BooksActions.loadBooksFromApi
. In the same action/reducer convert the field hasSearchedto false
when the API call is in progress,
This false
flag will stop these two messages from being displayed:
<h5 *ngIf="(hasSearched | async)">No. of books: {{ books.length }}</h5>
<p *ngIf="(hasSearched | async) && !books.length">No books found</p>
Make it true
to when it has been completed. As a reference, Your state and reducer will look like this then.
export interface BooksState {
books: any;
hasSearched: boolean;
}
export const initialState: BooksState = {
books: undefined,
hasSearched: undefined
};
The reducer will cases would look something like this.
case BooksActionsTypes.loadBooksFromApi:
return {
...state,
hasSearched: false
};
case BooksActionsTypes.loadBooksFromApiSuccessful:
return {
...state,
books: action.payload.books
hasSearched: true
};
For point number 4
What you are thinking Is correct in my understanding, clearing the state by dispatching an action on ngOnDestroy()
. It is the same way I use, to clear states when I want to have a fresh state on my next visit to the same component. It would be something similar to what I have written below
case BooksActionsTypes.ClearState:
return {
...state,
books: undefined
hasSearched: undefined
};
Upvotes: 5