diff --git a/commit_message b/commit_message
index 5c70b3046..6f8b2b6ad 100644
--- a/commit_message
+++ b/commit_message
@@ -1 +1 @@
-0ae21c92 feat(component-store): add migrator for `tapResponse` (#4321)
+22a3adf9 docs(store): fix lint issues in example code (#4324)
diff --git a/generated/docs/guide/store/walkthrough.json b/generated/docs/guide/store/walkthrough.json
index 0167efde5..a1a9df4e5 100644
--- a/generated/docs/guide/store/walkthrough.json
+++ b/generated/docs/guide/store/walkthrough.json
@@ -1,5 +1,5 @@
{
"id": "guide/store/walkthrough",
"title": "Walkthrough",
- "contents": "\n\n\n
\n
Walkthrough
\n
The following example more extensively utilizes the key concepts of store to manage the state of book list, and how the user can add a book to and remove it from their collection within an Angular component. Try the .
\n
Tutorial
\n
\n- Generate a new project using StackBlitz and create a folder named
book-list
inside the app
folder. This folder is used to hold the book list component later in the tutorial. For now, let's start with adding a file named books.model.ts
to reference different aspects of a book in the book list. \n
\n
\nexport interface Book {\n id: string;\n volumeInfo: {\n title: string;\n authors: Array<string>;\n };\n}\n\n\n
\n- Right click on the
app
folder to create a state management folder state
. Within the new folder, create a new file books.actions.ts
to describe the book actions. Book actions include the book list retrieval, and the add and remove book actions. \n
\n
\nimport { createActionGroup, props } from '@ngrx/store';\nimport { Book } from '../book-list/books.model';\n\nexport const BooksActions = createActionGroup({\n source: 'Books',\n events: {\n 'Add Book': props<{ bookId: string }>(),\n 'Remove Book': props<{ bookId: string }>(),\n },\n});\n\nexport const BooksApiActions = createActionGroup({\n source: 'Books API',\n events: {\n 'Retrieved Book List': props<{ books: ReadonlyArray<Book> }>(),\n },\n});\n\n\n\n
\n- Right click on the
state
folder and create a new file labeled books.reducer.ts
. Within this file, define a reducer function to handle the retrieval of the book list from the state and consequently, update the state. \n
\n
\nimport { createReducer, on } from '@ngrx/store';\n\nimport { BooksApiActions } from './books.actions';\nimport { Book } from '../book-list/books.model';\n\nexport const initialState: ReadonlyArray<Book> = [];\n\nexport const booksReducer = createReducer(\n initialState,\n on(BooksApiActions.retrievedBookList, (_state, { books }) => books)\n);\n\n\n\n
\n- Create another file named
collection.reducer.ts
in the state
folder to handle actions that alter the user's book collection. Define a reducer function that handles the add action by appending the book's ID to the collection, including a condition to avoid duplicate book IDs. Define the same reducer to handle the remove action by filtering the collection array with the book ID. \n
\n
\nimport { createReducer, on } from '@ngrx/store';\nimport { BooksActions } from './books.actions';\n\nexport const initialState: ReadonlyArray<string> = [];\n\nexport const collectionReducer = createReducer(\n initialState,\n on(BooksActions.removeBook, (state, { bookId }) =>\n state.filter((id) => id !== bookId)\n ),\n on(BooksActions.addBook, (state, { bookId }) => {\n if (state.indexOf(bookId) > -1) return state;\n\n return [...state, bookId];\n })\n);\n\n\n\n
\n- Import the
StoreModule
from @ngrx/store
and the books.reducer
and collection.reducer
file. \n
\n
\nimport { HttpClientModule } from '@angular/common/http';\nimport { booksReducer } from './state/books.reducer';\nimport { collectionReducer } from './state/collection.reducer';\nimport { StoreModule } from '@ngrx/store';\n\n\n
\n- Add the
StoreModule.forRoot
function in the imports
array of your AppModule
with an object containing the books
and booksReducer
, as well as the collection
and collectionReducer
that manage the state of the book list and the collection. The StoreModule.forRoot()
method registers the global providers needed to access the Store
throughout your application. \n
\n
\n@NgModule({\n imports: [\n BrowserModule,\n StoreModule.forRoot({ books: booksReducer, collection: collectionReducer }),\n HttpClientModule,\n ],\n declarations: [AppComponent],\n bootstrap: [AppComponent],\n })\n export class AppModule {}\n\n\n\n
\n- Create the book list and collection selectors to ensure we get the correct information from the store. As you can see, the
selectBookCollection
selector combines two other selectors in order to build its return value. \n
\n
\nimport { createSelector, createFeatureSelector } from '@ngrx/store';\nimport { Book } from '../book-list/books.model';\n\nexport const selectBooks = createFeatureSelector<ReadonlyArray<Book>>('books');\n\nexport const selectCollectionState = createFeatureSelector<\n ReadonlyArray<string>\n>('collection');\n\nexport const selectBookCollection = createSelector(\n selectBooks,\n selectCollectionState,\n (books, collection) => {\n return collection.map((id) => books.find((book) => book.id === id)!);\n }\n);\n\n\n\n
\n- In the
book-list
folder, we want to have a service that fetches the data needed for the book list from an API. Create a file in the book-list
folder named books.service.ts
, which will call the Google Books API and return a list of books. \n
\n
\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\n\nimport { of, Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { Book } from './books.model';\n\n@Injectable({ providedIn: 'root' })\nexport class GoogleBooksService {\n constructor(private http: HttpClient) {}\n\n getBooks(): Observable<Array<Book>> {\n return this.http\n .get<{ items: Book[] }>(\n 'https://www.googleapis.com/books/v1/volumes?maxResults=5&orderBy=relevance&q=oliver%20sacks'\n )\n .pipe(map((books) => books.items || []));\n }\n}\n\n\n\n
\n- In the same folder (
book-list
), create the BookListComponent
with the following template. Update the BookListComponent
class to dispatch the add
event. \n
\n
\n<div\n class=\"book-item\"\n *ngFor=\"let book of books\"\n>\n <p>{{book.volumeInfo.title}}</p><span> by {{book.volumeInfo.authors}}</span>\n <button\n (click)=\"add.emit(book.id)\"\n data-test=\"add-button\"\n >Add to Collection</button>\n</div>\n\n\n
\nimport { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { Book } from './books.model';\n\n@Component({\n selector: 'app-book-list',\n templateUrl: './book-list.component.html',\n styleUrls: ['./book-list.component.css'],\n})\nexport class BookListComponent {\n @Input() books: ReadonlyArray<Book> = [];\n @Output() add = new EventEmitter<string>();\n}\n\n\n\n
\n- Create a new Component named
book-collection
in the app
folder. Update the BookCollectionComponent
template and class. \n
\n
\n<div \n class=\"book-item\"\n *ngFor=\"let book of books\"\n>\n <p>{{book.volumeInfo.title}}</p><span> by {{book.volumeInfo.authors}}</span>\n <button\n (click)=\"remove.emit(book.id)\"\n data-test=\"remove-button\"\n >Remove from Collection</button>\n</div>\n\n\n\n
\nimport { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { Book } from '../book-list/books.model';\n\n@Component({\n selector: 'app-book-collection',\n templateUrl: './book-collection.component.html',\n styleUrls: ['./book-collection.component.css'],\n})\nexport class BookCollectionComponent {\n @Input() books: ReadonlyArray<Book> = [];\n @Output() remove = new EventEmitter<string>();\n}\n\n\n\n
\n- Add
BookListComponent
and BookCollectionComponent
to your AppComponent
template, and to your declarations (along with their top level import statements) in app.module.ts
as well. \n
\n
\n<h2>Books</h2>\n<app-book-list class=\"book-list\" [books]=\"(books$ | async)!\" (add)=\"onAdd($event)\"></app-book-list>\n\n<h2>My Collection</h2>\n<app-book-collection class=\"book-collection\" [books]=\"(bookCollection$ | async)!\" (remove)=\"onRemove($event)\">\n</app-book-collection>\n\n\n
\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { HttpClientModule } from '@angular/common/http';\nimport { booksReducer } from './state/books.reducer';\nimport { collectionReducer } from './state/collection.reducer';\nimport { StoreModule } from '@ngrx/store';\n\nimport { AppComponent } from './app.component';\nimport { BookListComponent } from './book-list/book-list.component';\nimport { BookCollectionComponent } from './book-collection/book-collection.component';\n\n@NgModule({\n imports: [\n BrowserModule,\n StoreModule.forRoot({ books: booksReducer, collection: collectionReducer }),\n HttpClientModule,\n ],\n declarations: [AppComponent, BookListComponent, BookCollectionComponent],\n bootstrap: [AppComponent],\n})\nexport class AppModule {}\n\n\n\n
\n- In the
AppComponent
class, add the selectors and corresponding actions to dispatch on add
or remove
method calls. Then subscribe to the Google Books API in order to update the state. (This should probably be handled by NgRx Effects, which you can read about here. For the sake of this demo, NgRx Effects is not being included). \n
\n
\nimport { Component } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\nimport { selectBookCollection, selectBooks } from './state/books.selectors';\nimport { BooksActions, BooksApiActions } from './state/books.actions';\nimport { GoogleBooksService } from './book-list/books.service';\n\n@Component({\n selector: 'app-root',\n templateUrl: './app.component.html',\n})\nexport class AppComponent {\n books$ = this.store.select(selectBooks);\n bookCollection$ = this.store.select(selectBookCollection);\n\n onAdd(bookId: string) {\n this.store.dispatch(BooksActions.addBook({ bookId }));\n }\n\n onRemove(bookId: string) {\n this.store.dispatch(BooksActions.removeBook({ bookId }));\n }\n\n constructor(private booksService: GoogleBooksService, private store: Store) {}\n\n ngOnInit() {\n this.booksService\n .getBooks()\n .subscribe((books) =>\n this.store.dispatch(BooksApiActions.retrievedBookList({ books }))\n );\n }\n}\n\n\n\n
And that's it! Click the add and remove buttons to change the state.
\n
Let's cover what you did:
\n
\n- Defined actions to express events.
\n- Defined two reducer functions to manage different parts of the state.
\n- Registered the global state container that is available throughout your application.
\n- Defined the state, as well as selectors that retrieve specific parts of the state.
\n- Created two distinct components, as well as a service that fetches from the Google Books API.
\n- Injected the
Store
and Google Books API services to dispatch actions and select the current state. \n
\n\n
\n\n\n"
+ "contents": "\n\n\n \n
Walkthrough
\n
The following example more extensively utilizes the key concepts of store to manage the state of book list, and how the user can add a book to and remove it from their collection within an Angular component. Try the .
\n
Tutorial
\n
\n- Generate a new project using StackBlitz and create a folder named
book-list
inside the app
folder. This folder is used to hold the book list component later in the tutorial. For now, let's start with adding a file named books.model.ts
to reference different aspects of a book in the book list. \n
\n
\nexport interface Book {\n id: string;\n volumeInfo: {\n title: string;\n authors: Array<string>;\n };\n}\n\n\n
\n- Right click on the
app
folder to create a state management folder state
. Within the new folder, create a new file books.actions.ts
to describe the book actions. Book actions include the book list retrieval, and the add and remove book actions. \n
\n
\nimport { createActionGroup, props } from '@ngrx/store';\nimport { Book } from '../book-list/books.model';\n\nexport const BooksActions = createActionGroup({\n source: 'Books',\n events: {\n 'Add Book': props<{ bookId: string }>(),\n 'Remove Book': props<{ bookId: string }>(),\n },\n});\n\nexport const BooksApiActions = createActionGroup({\n source: 'Books API',\n events: {\n 'Retrieved Book List': props<{ books: ReadonlyArray<Book> }>(),\n },\n});\n\n\n\n
\n- Right click on the
state
folder and create a new file labeled books.reducer.ts
. Within this file, define a reducer function to handle the retrieval of the book list from the state and consequently, update the state. \n
\n
\nimport { createReducer, on } from '@ngrx/store';\n\nimport { BooksApiActions } from './books.actions';\nimport { Book } from '../book-list/books.model';\n\nexport const initialState: ReadonlyArray<Book> = [];\n\nexport const booksReducer = createReducer(\n initialState,\n on(BooksApiActions.retrievedBookList, (_state, { books }) => books)\n);\n\n\n\n
\n- Create another file named
collection.reducer.ts
in the state
folder to handle actions that alter the user's book collection. Define a reducer function that handles the add action by appending the book's ID to the collection, including a condition to avoid duplicate book IDs. Define the same reducer to handle the remove action by filtering the collection array with the book ID. \n
\n
\nimport { createReducer, on } from '@ngrx/store';\nimport { BooksActions } from './books.actions';\n\nexport const initialState: ReadonlyArray<string> = [];\n\nexport const collectionReducer = createReducer(\n initialState,\n on(BooksActions.removeBook, (state, { bookId }) =>\n state.filter((id) => id !== bookId)\n ),\n on(BooksActions.addBook, (state, { bookId }) => {\n if (state.indexOf(bookId) > -1) return state;\n\n return [...state, bookId];\n })\n);\n\n\n\n
\n- Import the
StoreModule
from @ngrx/store
and the books.reducer
and collection.reducer
file. \n
\n
\nimport { HttpClientModule } from '@angular/common/http';\nimport { booksReducer } from './state/books.reducer';\nimport { collectionReducer } from './state/collection.reducer';\nimport { StoreModule } from '@ngrx/store';\n\n\n
\n- Add the
StoreModule.forRoot
function in the imports
array of your AppModule
with an object containing the books
and booksReducer
, as well as the collection
and collectionReducer
that manage the state of the book list and the collection. The StoreModule.forRoot()
method registers the global providers needed to access the Store
throughout your application. \n
\n
\n@NgModule({\n imports: [\n BrowserModule,\n StoreModule.forRoot({ books: booksReducer, collection: collectionReducer }),\n HttpClientModule,\n ],\n declarations: [AppComponent],\n bootstrap: [AppComponent],\n })\n export class AppModule {}\n\n\n\n
\n- Create the book list and collection selectors to ensure we get the correct information from the store. As you can see, the
selectBookCollection
selector combines two other selectors in order to build its return value. \n
\n
\nimport { createSelector, createFeatureSelector } from '@ngrx/store';\nimport { Book } from '../book-list/books.model';\n\nexport const selectBooks = createFeatureSelector<ReadonlyArray<Book>>('books');\n\nexport const selectCollectionState = createFeatureSelector<\n ReadonlyArray<string>\n>('collection');\n\nexport const selectBookCollection = createSelector(\n selectBooks,\n selectCollectionState,\n (books, collection) => {\n return collection.map((id) => books.find((book) => book.id === id)!);\n }\n);\n\n\n\n
\n- In the
book-list
folder, we want to have a service that fetches the data needed for the book list from an API. Create a file in the book-list
folder named books.service.ts
, which will call the Google Books API and return a list of books. \n
\n
\nimport { HttpClient } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\n\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { Book } from './books.model';\n\n@Injectable({ providedIn: 'root' })\nexport class GoogleBooksService {\n constructor(private http: HttpClient) {}\n\n getBooks(): Observable<Array<Book>> {\n return this.http\n .get<{ items: Book[] }>(\n 'https://www.googleapis.com/books/v1/volumes?maxResults=5&orderBy=relevance&q=oliver%20sacks'\n )\n .pipe(map((books) => books.items || []));\n }\n}\n\n\n\n
\n- In the same folder (
book-list
), create the BookListComponent
with the following template. Update the BookListComponent
class to dispatch the add
event. \n
\n
\n<div\n class=\"book-item\"\n *ngFor=\"let book of books\"\n>\n <p>{{book.volumeInfo.title}}</p><span> by {{book.volumeInfo.authors}}</span>\n <button\n (click)=\"add.emit(book.id)\"\n data-test=\"add-button\"\n >Add to Collection</button>\n</div>\n\n\n
\nimport { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { Book } from './books.model';\n\n@Component({\n selector: 'app-book-list',\n templateUrl: './book-list.component.html',\n styleUrls: ['./book-list.component.css'],\n})\nexport class BookListComponent {\n @Input() books: ReadonlyArray<Book> = [];\n @Output() add = new EventEmitter<string>();\n}\n\n\n\n
\n- Create a new Component named
book-collection
in the app
folder. Update the BookCollectionComponent
template and class. \n
\n
\n<div \n class=\"book-item\"\n *ngFor=\"let book of books\"\n>\n <p>{{book.volumeInfo.title}}</p><span> by {{book.volumeInfo.authors}}</span>\n <button\n (click)=\"remove.emit(book.id)\"\n data-test=\"remove-button\"\n >Remove from Collection</button>\n</div>\n\n\n\n
\nimport { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { Book } from '../book-list/books.model';\n\n@Component({\n selector: 'app-book-collection',\n templateUrl: './book-collection.component.html',\n styleUrls: ['./book-collection.component.css'],\n})\nexport class BookCollectionComponent {\n @Input() books: ReadonlyArray<Book> = [];\n @Output() remove = new EventEmitter<string>();\n}\n\n\n\n
\n- Add
BookListComponent
and BookCollectionComponent
to your AppComponent
template, and to your declarations (along with their top level import statements) in app.module.ts
as well. \n
\n
\n<h2>Books</h2>\n<app-book-list class=\"book-list\" [books]=\"(books$ | async)!\" (add)=\"onAdd($event)\"></app-book-list>\n\n<h2>My Collection</h2>\n<app-book-collection class=\"book-collection\" [books]=\"(bookCollection$ | async)!\" (remove)=\"onRemove($event)\">\n</app-book-collection>\n\n\n
\nimport { NgModule } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\n\nimport { HttpClientModule } from '@angular/common/http';\nimport { booksReducer } from './state/books.reducer';\nimport { collectionReducer } from './state/collection.reducer';\nimport { StoreModule } from '@ngrx/store';\n\nimport { AppComponent } from './app.component';\nimport { BookListComponent } from './book-list/book-list.component';\nimport { BookCollectionComponent } from './book-collection/book-collection.component';\n\n@NgModule({\n imports: [\n BrowserModule,\n StoreModule.forRoot({ books: booksReducer, collection: collectionReducer }),\n HttpClientModule,\n ],\n declarations: [AppComponent, BookListComponent, BookCollectionComponent],\n bootstrap: [AppComponent],\n})\nexport class AppModule {}\n\n\n\n
\n- In the
AppComponent
class, add the selectors and corresponding actions to dispatch on add
or remove
method calls. Then subscribe to the Google Books API in order to update the state. (This should probably be handled by NgRx Effects, which you can read about here. For the sake of this demo, NgRx Effects is not being included). \n
\n
\nimport { Component, OnInit } from '@angular/core';\nimport { Store } from '@ngrx/store';\n\nimport { selectBookCollection, selectBooks } from './state/books.selectors';\nimport { BooksActions, BooksApiActions } from './state/books.actions';\nimport { GoogleBooksService } from './book-list/books.service';\n\n@Component({\n selector: 'app-root',\n templateUrl: './app.component.html',\n})\nexport class AppComponent implements OnInit {\n books$ = this.store.select(selectBooks);\n bookCollection$ = this.store.select(selectBookCollection);\n\n onAdd(bookId: string) {\n this.store.dispatch(BooksActions.addBook({ bookId }));\n }\n\n onRemove(bookId: string) {\n this.store.dispatch(BooksActions.removeBook({ bookId }));\n }\n\n constructor(private booksService: GoogleBooksService, private store: Store) {}\n\n ngOnInit() {\n this.booksService\n .getBooks()\n .subscribe((books) =>\n this.store.dispatch(BooksApiActions.retrievedBookList({ books }))\n );\n }\n}\n\n\n\n
And that's it! Click the add and remove buttons to change the state.
\n
Let's cover what you did:
\n
\n- Defined actions to express events.
\n- Defined two reducer functions to manage different parts of the state.
\n- Registered the global state container that is available throughout your application.
\n- Defined the state, as well as selectors that retrieve specific parts of the state.
\n- Created two distinct components, as well as a service that fetches from the Google Books API.
\n- Injected the
Store
and Google Books API services to dispatch actions and select the current state. \n
\n\n
\n\n\n"
}
\ No newline at end of file
diff --git a/generated/live-examples/store-walkthrough/stackblitz.html b/generated/live-examples/store-walkthrough/stackblitz.html
index b230073ce..d05321fa6 100644
--- a/generated/live-examples/store-walkthrough/stackblitz.html
+++ b/generated/live-examples/store-walkthrough/stackblitz.html
@@ -1,5 +1,5 @@
-