# Feature Creators
## What is an NgRx feature?
There are three main building blocks of global state management with `@ngrx/store`: actions, reducers, and selectors.
For a particular feature state, we create a reducer for handling state transitions based on the dispatched actions
and selectors to obtain slices of the feature state. Also, we need to define a feature name needed to register
the feature reducer in the NgRx store. Therefore, we can consider the NgRx feature as a grouping of the feature name,
feature reducer, and selectors for the particular feature state.
## Using feature creator
The `createFeature` function reduces repetitive code in selector files by generating a feature selector and child selectors
for each feature state property. It accepts an object containing a feature name and a feature reducer as the input argument:
```ts
import { createFeature, createReducer, on } from '@ngrx/store';
import { Book } from './book.model';
import * as BookListPageActions from './book-list-page.actions';
import * as BooksApiActions from './books-api.actions';
interface State {
books: Book[];
loading: boolean;
}
const initialState: State = {
books: [],
loading: false,
};
export const booksFeature = createFeature({
name: 'books',
reducer: createReducer(
initialState,
on(BookListPageActions.enter, (state) => ({
...state,
loading: true,
})),
on(BooksApiActions.loadBooksSuccess, (state, { books }) => ({
...state,
books,
loading: false,
}))
),
});
export const {
name, // feature name
reducer, // feature reducer
selectBooksState, // feature selector
selectBooks, // selector for `books` property
selectLoading, // selector for `loading` property
} = booksFeature;
```
An object created with the `createFeature` function contains a feature name, a feature reducer, a feature selector,
and a selector for each feature state property. All generated selectors have the "select" prefix, and the feature selector has
the "State" suffix. In this example, the name of the feature selector is `selectBooksState`, where "books" is the feature name.
The names of the child selectors are `selectBooks` and `selectLoading`, based on the property names of the books feature state.
The generated selectors can be used independently or to create other selectors:
```ts
import { createSelector } from '@ngrx/store';
import { booksFeature } from './books.reducer';
export const selectBookListPageViewModel = createSelector(
booksFeature.selectBooks,
booksFeature.selectLoading,
(books, loading) => ({ books, loading })
);
```
## Providing Extra Selectors
`createFeature` also can be used to provide extra selectors for the feature state with the `extraSelectors` option:
```ts
import { createFeature, createReducer, on } from '@ngrx/store';
import { Book } from './book.model';
import * as BookListPageActions from './book-list-page.actions';
interface State {
books: Book[];
query: string;
}
const initialState: State = {
books: [],
query: '',
};
export const booksFeature = createFeature({
name: 'books',
reducer: createReducer(
initialState,
on(BookListPageActions.search, (state, action) => ({
...state,
query: action.query,
}))
),
extraSelectors: ({ selectQuery, selectBooks }) => ({
selectFilteredBooks: createSelector(
selectQuery,
selectBooks,
(query, books) =>
books.filter((book) => book.title.includes(query))
),
}),
});
```
The `extraSelectors` option accepts a function that takes the generated selectors as input arguments and returns an object of all the extra selectors. We can use it to define as many extra selectors as we need.
### Reusing Extra Selectors
Reusing extra selectors can be done by defining `extraSelectors` factory in the following way:
```ts
import { createFeature, createReducer, on } from '@ngrx/store';
import { Book } from './book.model';
import * as BookListPageActions from './book-list-page.actions';
interface State {
books: Book[];
query: string;
}
const initialState: State = {
books: [],
query: '',
};
export const booksFeature = createFeature({
name: 'books',
reducer: createReducer(
initialState,
on(BookListPageActions.search, (state, action) => ({
...state,
query: action.query,
}))
),
extraSelectors: ({ selectQuery, selectBooks }) => {
const selectFilteredBooks = createSelector(
selectQuery,
selectBooks,
(query, books) =>
books.filter((book) => book.title.includes(query))
);
const selectFilteredBooksWithRating = createSelector(
selectFilteredBooks,
(books) => books.filter((book) => book.ratingsCount >= 1)
);
return { selectFilteredBooks, selectFilteredBooksWithRating };
},
});
```
## Feature registration
Registering the feature reducer in the store can be done by passing the entire feature object to the `StoreModule.forFeature` method:
```ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { booksFeature } from './books.reducer';
@NgModule({
imports: [StoreModule.forFeature(booksFeature)],
})
export class BooksModule {}
```
### Using the Standalone API
Registering the feature can be done using the standalone APIs if you are bootstrapping an Angular application using standalone features.
```ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideStore, provideState } from '@ngrx/store';
import { AppComponent } from './app.component';
import { booksFeature } from './books.reducer';
bootstrapApplication(AppComponent, {
providers: [provideStore(), provideState(booksFeature)],
});
```
Feature states can also be registered in the `providers` array of the route config.
```ts
import { Route } from '@angular/router';
import { provideState } from '@ngrx/store';
import { booksFeature } from './books.reducer';
export const routes: Route[] = [
{
path: 'books',
providers: [provideState(booksFeature)],
},
];
```
## Restrictions
The `createFeature` function cannot be used for features whose state contains optional properties.
In other words, all state properties have to be passed to the initial state object.
So, if the state contains optional properties:
```ts
interface State {
books: Book[];
activeBookId?: string;
}
const initialState: State = {
books: [],
};
```
Each optional symbol (`?`) have to be replaced with `| null` or `| undefined`:
```ts
interface State {
books: Book[];
activeBookId: string | null;
// or activeBookId: string | undefined;
}
const initialState: State = {
books: [],
activeBookId: null,
// or activeBookId: undefined,
};
```