NgRx/Entity with Example
This Article will walk through the NgRx/Entity example.
1. The @ngrx/entity
library manages collections of entities. It provides APIs to manipulate and query entity collections.
2. The @ngrx/entity
library helps to reduce boilerplate coding of reducers that manage a collections of entities.
3. The @ngrx/entity
can sort the collection for the given entity property. When we disable sorting, @ngrx/entity
provides better performance in CRUD operations for managing entity collections.
4. The @ngrx/entity
provides EntityState
, EntityAdapter
interfaces and createEntityAdapter
method.
5. Entity state is created by extending EntityState
interface. The EntityAdapter
is instantiated using createEntityAdapter
method.
6. The EntityAdapter
provides methods to add, update and remove entities from collections.
7. The EntityAdapter
provides getInitialState
and getSelectors
methods. The getInitialState
provides initial state of our entity state. The getSelectors
is used to create selectors to query entity collections.
Here on this page we will provide complete example to add, update, remove and select the entities from the collections step-by-step.
1. Technologies Used
Find the technologies being used in our example.
1. Angular
2. NgRx
3. Node.js
4. NPM
2. Install NgRx
We will install Store, Effects and Entity using NPM command as following.
1. To install @ngrx/store
, run following command.
npm install @ngrx/store --save
2. To install @ngrx/effects
, run following command.
npm install @ngrx/effects --save
3. To install @ngrx/entity
, run following command.
npm install @ngrx/entity --save
4. In our application, we will use angular-in-memory-web-api to load data for testing purpose. To install it, run following command.
npm install angular-in-memory-web-api@0.13.0 --save
Find the print screen of sample output of our application.
3. Entity State
NgRx provides EntityState
interface that is a predefined generic interface for a given entity collection. The EntityState
has following attributes.
ids : Array of all primary ids in collection.
entities : A dictionary of collection items indexed by primary id.
Our State needs to extend EntityState
to use it. Suppose we have a class as following.
article.ts
export class Article {
id = '';
title = '';
category = '';
}
Now we will create our State extending EntityState
as following.
app.states.ts
export interface ArticleState extends EntityState<Article> {
//Other entity state properties
}
4. Entity Adapter
NgRx has EntityAdapter
interface that provides many collection methods for managing the entity state. It is instantiated using NgRx createEntityAdapter
method as following.
article.adapter.ts
import { EntityAdapter, createEntityAdapter } from '@ngrx/entity';
export const adapter: EntityAdapter<Article> = createEntityAdapter<Article>();
4.1 createEntityAdapter
The createEntityAdapter
method instantiates generic EntityAdapter
for a single entity state collection as given below.
export const adapter: EntityAdapter<Article> = createEntityAdapter<Article>();
The createEntityAdapter
method can accept optional argument i.e. an object with properties selectId
and sortComparer
.
selectId : Selects primary id for the collection.
sortComparer : Comparer function used to sort the collection. We should use this property if we want to sort the collection before displaying.
4.2 Sort Comparer
Here we will provide code snippet how to use sortComparer
with createEntityAdapter
. First we will define a comparer method. In our example we have article entity and suppose we want to sort collection on the basis of category of artcle. We will create a method to sort entity on the basis of article category. Then we will assign this comparer method to sortComparer
property while instantiating EntityAdapter
using createEntityAdapter
. Find the code snippet.
article.adapter.ts
export function sortByCategory(ob1: Article, ob2: Article): number {
return ob1.category.localeCompare(ob2.category);
}
export const adapter: EntityAdapter<Article> = createEntityAdapter<Article>({
sortComparer: sortByCategory
});
To leave collection unsorted, we need to assign false value to sortComparer
property.
export const adapter: EntityAdapter<Article> = createEntityAdapter<Article>({
sortComparer: false
});
To get high performance in CRUD operations, it is good to assign false value to sortComparer
property.
4.3 Entity Adapter Methods
Here we will discuss EntityAdapter
methods. It has getInitialState
method to get initial state of the entity, getSelectors
method to get entity selectors. The EntityAdapter
extends EntityStateAdapter
and inherits its methods to add, update and remove entities. Find the methods of EntityAdapter
.
4.3.1 getInitialState
The getInitialState
method returns initial state of our entity state.
article.reducer.ts
export const initialState: ArticleState = adapter.getInitialState({
//Initialize other entity state properties
});
4.3.2 getSelectors
The getSelectors
method returns NgRx EntitySelectors
that provides functions for selecting information from the collection of entities. The functions of EntitySelectors
are as following.
selectIds: Selects array of ids.
selectEntities: Selects the dictionary of entities. We can use it to fetch entity by id.
selectAll: Selects array of all entities.
selectTotal: Selects the total count of entities.
In our example we will work with articles. Find the sample entity selectors using getSelectors
for article.
article.adapter.ts
export const {
selectIds: selectArticleIds,
selectEntities: selectArticleEntities,
selectAll: selectAllArticles,
selectTotal: articlesCount
} = adapter.getSelectors();
4.3.3 Add, Update and Remove
EntityAdapter
extends EntityStateAdapter
and inherits following methods.
addOne : Adds one entity to the collection.
addMany : Adds multiple entities to the collection.
updateOne : Updates one entity in the collection against an id using NgRx Update
type.
updateMany : Updates multiple entities in the collection against given ids using array of Update
type.
removeOne : Removes one entity from the collection for the given id.
removeMany : Removes multiple entities from the collection for the given array of ids.
removeAll : Removes all entities from collection.
5. addOne and addMany
Here we will discuss EntityAdapter
methods to add entities. These methods are addOne
and addMany
. The addOne
adds one entity to the collection, addMany
adds many entities to the collection.
We need to pass arguments to these methods as following.
addOne : Pass instance of entity and state.
addMany : Pass array of entities and state.
Now find the reducer code snippet to use addOne
and addMany
.
const _articleReducer = createReducer(
initialState,
on(fromActions.AddArticle, (state, {payload}) => fromAdapter.adapter.addOne(payload.article, state)),
on(fromActions.AddArticles, (state, {payload}) => fromAdapter.adapter.addMany(payload.articles, state))
);
6. updateOne and updateMany using Update Type
To update entity in collection, EntityAdapter
provides updateOne
and updateMany
methods. The updateOne
updates one entity and updateMany
updates many entities. The updateOne
and updateMany
methods accept argument of NgRx Update
type and state.Update
type has two properties.
id: Id of entity which needs to be updated.
changes: Modified entity.
updateOne
and updateMany
methods will accept arguments as follows.
updateOne: Pass instance of Update
and state.
updateMany: Pass array of Update
and state.
Create Action
using Update
of NgRx/Entity.
export const UpdateArticle = createAction(ArticleActionTypes.UPDATE_ARTICLE,
props<{payload: { article: Update<Article> }}>());
export const UpdateArticles = createAction(ArticleActionTypes.UPDATE_ARTICLES,
props<{payload: { articles: Update<Article>[] }}>());
Find the reducer code snippet to use updateOne
and updateMany
.
const _articleReducer = createReducer(
initialState,
on(fromActions.UpdateArticle, (state, {payload}) => fromAdapter.adapter.updateOne(payload.article, state)),
on(fromActions.UpdateArticles, (state, {payload}) => fromAdapter.adapter.updateMany(payload.articles, state))
);
7. removeOne, removeMany and removeAll
To remove entities from collection, EntityAdapter
provides removeOne
, removeMany
and removeAll
methods. The removeOne
removes one entity, removeMany
removes many entities and removeAll
clears the collection by removing all entities.
These methods accepts arguments as following.
removeOne: Pass entity id and state.
removeMany: Pass array of entity ids and state.
removeAll: Pass state.
Find the reducer code snippet to use removeOne
, removeMany
and removeAll
.
const _articleReducer = createReducer(
initialState,
on(fromActions.RemoveArticle, (state, {payload}) => fromAdapter.adapter.removeOne(payload.id, state)),
on(fromActions.RemoveArticles, (state, {payload}) => fromAdapter.adapter.removeMany(payload.ids, state)),
on(fromActions.ClearArticles, (state) => fromAdapter.adapter.removeAll({ ...state, selectedArticleId: '' }))
);
8. Entity Selectors
Here we will create our selectors to fetch entities from collection. We have already discussed to initialize EntitySelectors
above using getSelectors
method of EntityAdapter
. We initialize EntitySelectors
as following.
article.adapter.ts
export const {
selectIds: selectArticleIds,
selectEntities: selectArticleEntities,
selectAll: selectAllArticles,
selectTotal: articlesCount
} = adapter.getSelectors();
Now we will create our selectors as given below.
article.reducer.ts
import * as fromAdapter from './article.adapter';
export const getArticleState = createFeatureSelector<ArticleState>('articleState');
export const selectArticleIds = createSelector(getArticleState, fromAdapter.selectArticleIds);
export const selectArticleEntities = createSelector(getArticleState, fromAdapter.selectArticleEntities);
export const selectAllArticles = createSelector(getArticleState, fromAdapter.selectAllArticles);
export const articlesCount = createSelector(getArticleState, fromAdapter.articlesCount);
We will use our selectors in component as following.
constructor(private store: Store<ArticleState>) {
this.count$ = store.select(fromReducer.articlesCount);
this.allArticles$ = store.select(fromReducer.selectAllArticles);
this.articleIds$ = store.select(fromReducer.selectArticleIds);
this.articleById$ = store.select(fromReducer.selectCurrentArticle);
}
9. Select by Id
We will discuss here how to select entity by id from collection. We need to follow below steps.
Step-1: Create a property for id in our entity state. In our example we have state for article.
app.states.ts
export interface ArticleState extends EntityState<Article> {
selectedArticleId: string;
}
Here we have created a property selectedArticleId
using which we will fetch entity.
Step-2: Initialize selectedArticleId
while creating initial state.
article.reducer.ts
import * as fromAdapter from './article.adapter';
export const initialState: ArticleState = fromAdapter.adapter.getInitialState({
selectedArticleId: ''
});
Step-3: Create reducer.
article.reducer.ts
const _articleReducer = createReducer(
initialState,
on(fromActions.SelectArticle, (state, {payload}) => Object.assign({ ...state, selectedArticleId: payload.articleId }))
);
Step-4: Create selector.
article.reducer.ts
export const getArticleState = createFeatureSelector<ArticleState>('articleState');
export const getSelectedArticleId = (state: ArticleState) => state.selectedArticleId;
export const selectCurrentArticleId = createSelector(getArticleState, getSelectedArticleId);
export const selectCurrentArticle = createSelector(
selectArticleEntities,
selectCurrentArticleId,
(articleEntities, articleId) => articleEntities[articleId]
);
In the above createSelector
method, the parameters (articleEntities, articleId)
will be filled in following way.selectArticleEntities
will assign value to articleEntities
.selectCurrentArticleId
will assign value to articleId
And finally articleEntities[articleId]
will return entity for the given id.
Step-5: Use selector in component to get entity by id.
constructor(private store: Store<ArticleState>) {
this.articleById$ = store.select(fromReducer.selectCurrentArticle);
------
}
10. Angular In-Memory Web API
We are using In-Memory Web API to load data into entity collection. To work with Angular In-Memory Web API, find the steps.
Step-1: Create a class that will implement InMemoryDbService
. Define createDb()
method with some dummy data.
test-data.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class TestData implements InMemoryDbService {
createDb() {
let articleDetails = [
{id: 'j1', title: 'Core Java Tutorial', category: 'Java'},
{id: 'a1', title: 'Angular Tutorial', category: 'Angular'},
];
return { articles: articleDetails };
}
}
We will get following Web Service URL.
/api/articles
Step-2: Before using In-Memory Web API, we need to import InMemoryWebApiModule
in application module and configure TestData
class as following.
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { TestData } from './test-data';
@NgModule({
imports: [
------
InMemoryWebApiModule.forRoot(TestData)
],
------
})
export class AppModule { }
11. Complete Example
Find the project structure of our application.
my-app
|
|--src
| |
| |--app
| | |
| | |--models
| | | |
| | | |--article.ts
| | |
| | |--actions
| | | |
| | | |--article.actions.ts
| | |
| | |--states
| | | |
| | | |--app.states.ts
| | |
| | |--reducers
| | | |
| | | |--article.adapter.ts
| | | |--article.reducer.ts
| | | |--index.ts
| | |
| | |--effects
| | | |
| | | |--article.effects.ts
| | |
| | |--services
| | | |
| | | |--article.service.ts
| | |
| | |--components
| | | |
| | | |--article.component.html
| | | |--article.component.ts
| | |
| | |--app.component.ts
| | |--app.module.ts
| | |--test-data.ts
| |
| |--index.html
| |--styles.css
|--package.json
Now find the complete code.
article.ts
export class Article {
id = '';
title = '';
category = '';
}
article.actions.ts
import { createAction, props } from '@ngrx/store';
import { Update } from '@ngrx/entity';
import { Article } from '../models/article';
enum ArticleActionTypes {
ADD_ARTICLE = '[ARTICLE] Add Article',
ADD_ARTICLES = '[ARTICLE] Add Articles',
UPDATE_ARTICLE = '[ARTICLE] Update Article',
UPDATE_ARTICLES = '[ARTICLE] Update Articles',
REMOVE_ARTICLE = '[ARTICLE] Remove Article',
REMOVE_ARTICLES = '[ARTICLE] Remove Articles',
CLEAR_ARTICLES = '[ARTICLE] Clear Articles',
LOAD_ALL_ARTICLES = '[ARTICLE] Load All Articles',
LOAD_ALL_ARTICLES_SUCCESS = '[ARTICLE] Load All Articles Success',
SELECT_ARTICLE = '[ARTICLE] Article By Id'
}
export const AddArticle = createAction(ArticleActionTypes.ADD_ARTICLE,
props<{payload: { article: Article }}>());
export const AddArticles = createAction(ArticleActionTypes.ADD_ARTICLES,
props<{payload: { articles: Article[] }}>());
export const UpdateArticle = createAction(ArticleActionTypes.UPDATE_ARTICLE,
props<{payload: { article: Update<Article> }}>());
export const UpdateArticles = createAction(ArticleActionTypes.UPDATE_ARTICLES,
props<{payload: { articles: Update<Article>[] }}>());
export const RemoveArticle = createAction(ArticleActionTypes.REMOVE_ARTICLE,
props<{payload: { id: string }}>());
export const RemoveArticles = createAction(ArticleActionTypes.REMOVE_ARTICLES,
props<{payload: { ids: string[] }}>());
export const ClearArticles = createAction(ArticleActionTypes.CLEAR_ARTICLES);
export const LoadArticles = createAction(ArticleActionTypes.LOAD_ALL_ARTICLES);
export const LoadArticlesSuccess = createAction(ArticleActionTypes.LOAD_ALL_ARTICLES_SUCCESS,
props<{payload: { articles: Article[] }}>());
export const SelectArticle = createAction(ArticleActionTypes.SELECT_ARTICLE,
props<{payload: { articleId: string }}>());
app.states.ts
import { Article } from '../models/article';
import { EntityState } from '@ngrx/entity';
export interface AppState {
articleState: ArticleState;
}
export interface ArticleState extends EntityState<Article> {
selectedArticleId: string;
}
article.adapter.ts
import { EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import { Article } from '../models/article';
export function sortByCategory(ob1: Article, ob2: Article): number {
return ob1.category.localeCompare(ob2.category);
}
export const adapter: EntityAdapter<Article> = createEntityAdapter<Article>({
sortComparer: sortByCategory
});
export const {
selectIds: selectArticleIds,
selectEntities: selectArticleEntities,
selectAll: selectAllArticles,
selectTotal: articlesCount
} = adapter.getSelectors();
article.reducer.ts
import { createFeatureSelector, createSelector, createReducer, on, Action } from '@ngrx/store';
import * as fromActions from '../actions/article.actions';
import { ArticleState } from '../states/app.states';
import * as fromAdapter from './article.adapter';
export const initialState: ArticleState = fromAdapter.adapter.getInitialState({
selectedArticleId: ''
});
// Creating reducer
const _articleReducer = createReducer(
initialState,
on(fromActions.AddArticle, (state, {payload}) => fromAdapter.adapter.addOne(payload.article, state)),
on(fromActions.AddArticles, (state, {payload}) => fromAdapter.adapter.addMany(payload.articles, state)),
on(fromActions.UpdateArticle, (state, {payload}) => fromAdapter.adapter.updateOne(payload.article, state)),
on(fromActions.UpdateArticles, (state, {payload}) => fromAdapter.adapter.updateMany(payload.articles, state)),
on(fromActions.RemoveArticle, (state, {payload}) => fromAdapter.adapter.removeOne(payload.id, state)),
on(fromActions.RemoveArticles, (state, {payload}) => fromAdapter.adapter.removeMany(payload.ids, state)),
on(fromActions.ClearArticles, (state) => fromAdapter.adapter.removeAll({ ...state, selectedArticleId: '' })),
on(fromActions.LoadArticlesSuccess, (state, {payload}) => {
state = fromAdapter.adapter.removeAll({ ...state, selectedArticleId: '' });
return fromAdapter.adapter.addMany(payload.articles, state);
}),
on(fromActions.SelectArticle, (state, {payload}) => Object.assign({ ...state, selectedArticleId: payload.articleId })),
);
export function articleReducer(state: any, action: Action) {
return _articleReducer(state, action);
}
// Creating selectors
export const getSelectedArticleId = (state: ArticleState) => state.selectedArticleId;
export const getArticleState = createFeatureSelector<ArticleState>('articleState');
export const selectArticleIds = createSelector(getArticleState, fromAdapter.selectArticleIds);
export const selectArticleEntities = createSelector(getArticleState, fromAdapter.selectArticleEntities);
export const selectAllArticles = createSelector(getArticleState, fromAdapter.selectAllArticles);
export const articlesCount = createSelector(getArticleState, fromAdapter.articlesCount);
export const selectCurrentArticleId = createSelector(getArticleState, getSelectedArticleId);
export const selectCurrentArticle = createSelector(
selectArticleEntities,
selectCurrentArticleId,
(articleEntities, articleId) => articleEntities[articleId]
);
index.ts
import { ActionReducerMap } from '@ngrx/store';
import { AppState } from '../states/app.states';
import * as reducer from './article.reducer';
export const reducers: ActionReducerMap<AppState> = {
articleState: reducer.articleReducer
};
article.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map, switchMap } from 'rxjs/operators';
import * as fromActions from '../actions/article.actions';
import { ArticleService } from '../services/article.service';
@Injectable()
export class ArticleEffects {
constructor(
private actions$: Actions,
private articleService: ArticleService
) { }
loadAllArticles$ = createEffect(() => this.actions$.pipe(
ofType(fromActions.LoadArticles),
switchMap(() =>
this.articleService.getAllArticles().pipe(
map(data => fromActions.LoadArticlesSuccess({ payload: { articles: data } }))
)
)
));
}
article.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Article } from '../models/article';
@Injectable()
export class ArticleService {
url = "/api/articles";
constructor(private http: HttpClient) { }
getAllArticles(): Observable<Article[]> {
return this.http.get<Article[]>(this.url);
}
}
article.component.html
<button (click)="loadAllArticles()">Show All Articles</button>
<button (click)="addArticleView()">Add Article</button>
<button (click)="updateArticleView()">Update Article</button>
<button (click)="removeArticleView()">Remove Article</button>
<button (click)="articleByIdView()">Select Article By Id</button>
<hr />
<div [ngSwitch]="task">
<ng-template ngSwitchCase="all">
<b>Total Count: {{count$ | async}}</b>
<br /><br /><b>Ids:</b> {{articleIds$ | async}}
<br /><br /><b>Details:</b>
<ul>
<li *ngFor="let article of allArticles$ | async">
{{article.id}} - {{article.title}} - {{article.category}}
</li>
</ul>
</ng-template>
<ng-template ngSwitchCase="add">
<form [formGroup]="articleForm" (ngSubmit)="onFormSubmitForAdd()">
<div formArrayName="articlesArray">
<div *ngFor="let acl of articlesFormArray.controls; let idx = index" [formGroupName]="idx">
<p> <b>New Article: {{idx + 1}}</b> </p>
<p> New Id: <input formControlName="id"></p>
<p> Title: <input formControlName="title"> </p>
<p> Category: <input formControlName="category"> </p>
<p> <button type="button" (click)="deleteFormArrayControl(idx)">Delete</button></p>
</div>
</div>
<button type="button" (click)="addMoreControlForAdd()">Add More Article</button>
<hr />
<p *ngIf="articlesFormArray.length > 0"> <button> Submit </button> </p>
</form>
</ng-template>
<ng-template ngSwitchCase="update">
<form [formGroup]="articleForm" (ngSubmit)="onFormSubmitForUpdate()">
<div formArrayName="articlesArray">
<div *ngFor="let acl of articlesFormArray.controls; let idx = index" [formGroupName]="idx">
<p> Id: <input formControlName="id" readonly></p>
<p> Update Title: <input formControlName="title"> </p>
<p> Update Category: <input formControlName="category"> </p>
<p> <button type="button" (click)="deleteFormArrayControl(idx)">Delete</button> </p>
</div>
</div>
<button type="button" (click)="addMoreControlForUpdate()">Update More Article</button>
<hr />
<p *ngIf="articlesFormArray.length > 0"> <button> Update </button> </p>
</form>
</ng-template>
<ng-template ngSwitchCase="remove">
<form [formGroup]="articleForm" (ngSubmit)="onFormSubmitForRemove()">
<div formArrayName="articlesArray">
<ul>
<li *ngFor="let acl of articlesFormArray.controls; let idx = index" [formGroupName]="idx">
<input type="checkbox" formControlName="chk" />
{{acl.get('articleData')?.value?.id}}
| {{acl.get('articleData')?.value?.title}}
| {{acl.get('articleData')?.value?.category}}
<input type="hidden" formControlName="articleData">
</li>
</ul>
</div>
<hr />
<p *ngIf="articlesFormArray.length > 0">
<button>Remove Selected</button> <button type="button" (click)="clearAllArticles()">Clear All</button>
</p>
</form>
</ng-template>
<ng-template ngSwitchCase="select">
<p> Enter Id: <input [(ngModel)]="articleId">
<button type="button" (click)="selectArticleById()">Show Article</button>
</p>
<ul>
<li *ngIf="articleById$ | async as article">
{{article.id}} - {{article.title}} - {{article.category}}
</li>
</ul>
</ng-template>
</div>
article.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, FormArray } from '@angular/forms'
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import * as fromReducer from '../reducers/article.reducer';
import * as fromActions from '../actions/article.actions';
import { ArticleState } from '../states/app.states';
import { Article } from '../models/article';
@Component({
selector: 'app-article',
templateUrl: 'article.component.html'
})
export class ArticleComponent implements OnInit {
allArticles$: Observable<Article[]>;
articleById$: Observable<Article | undefined>;
count$: Observable<number>;
articleIds$: Observable<string[] | number[]>;
task = '';
articleId = '';
articleForm = {} as FormGroup;
constructor(
private formBuilder: FormBuilder,
private store: Store<ArticleState>) {
this.count$ = store.select(fromReducer.articlesCount);
this.allArticles$ = store.select(fromReducer.selectAllArticles);
this.articleIds$ = store.select(fromReducer.selectArticleIds);
this.articleById$ = store.select(fromReducer.selectCurrentArticle);
}
ngOnInit() {
this.store.dispatch(fromActions.LoadArticles());
}
createBlankArticleForm() {
this.articleForm = this.formBuilder.group({
articlesArray: this.formBuilder.array([])
});
}
createArticleFormForAdd() {
this.createBlankArticleForm();
this.addMoreControlForAdd();
}
get articlesFormArray(): FormArray {
return this.articleForm.get('articlesArray') as FormArray;
}
addMoreControlForAdd() {
let ag = this.formBuilder.group(new Article());
this.articlesFormArray.push(ag);
}
updateArticleForm() {
this.createBlankArticleForm();
this.allArticles$.subscribe(articles => {
if (articles && articles.length > 0) {
let article = articles[0];
let ag = this.formBuilder.group(article);
this.articlesFormArray.push(ag);
}
});
}
addMoreControlForUpdate() {
this.allArticles$.subscribe(articles => {
if (articles && articles.length > 0 && this.articlesFormArray.length < articles.length) {
let len = this.articlesFormArray.length;
let article = articles[len];
let ag = this.formBuilder.group(article);
this.articlesFormArray.push(ag);
}
});
}
deleteFormArrayControl(idx: number) {
this.articlesFormArray.removeAt(idx);
}
addArticleView() {
this.task = 'add';
this.createArticleFormForAdd();
}
updateArticleView() {
this.task = 'update';
this.updateArticleForm();
}
removeArticleView() {
this.task = 'remove';
this.createBlankArticleForm();
this.allArticles$.subscribe(articles => {
this.createBlankArticleForm();
articles.forEach(article => {
let ag = this.formBuilder.group({
articleData: article,
chk: false
});
this.articlesFormArray.push(ag);
});
});
}
articleByIdView() {
this.task = 'select';
}
onFormSubmitForAdd() {
if (this.articlesFormArray.length === 1) {
this.addArticle(this.articlesFormArray.at(0).value);
} else if (this.articlesFormArray.length > 1) {
this.addArticles(this.articlesFormArray.value);
}
this.createBlankArticleForm();
this.loadAllArticles();
}
onFormSubmitForUpdate() {
if (this.articlesFormArray.length === 1) {
this.updateArticle(this.articlesFormArray.at(0).value);
} else if (this.articlesFormArray.length > 1) {
this.updateArticles(this.articlesFormArray.value);
}
this.createBlankArticleForm();
this.loadAllArticles();
}
onFormSubmitForRemove() {
let articleIdsToDelete: string[] = [];
this.articlesFormArray.controls.forEach(result => {
if (result.get('chk')?.value) {
articleIdsToDelete.push(result.get('articleData')?.value.id);
}
});
if (articleIdsToDelete.length == 1) {
this.removeArticle(articleIdsToDelete[0]);
} else if (articleIdsToDelete.length > 1) {
this.removeArticles(articleIdsToDelete);
}
}
addArticle(data: Article) {
this.store.dispatch(fromActions.AddArticle({ payload: { article: data } }));
}
addArticles(data: Article[]) {
this.store.dispatch(fromActions.AddArticles({ payload: { articles: data } }));
}
updateArticle(data: Article) {
this.store.dispatch(fromActions.UpdateArticle({ payload: { article: { id: data.id, changes: data } } }));
}
updateArticles(data: Article[]) {
let allUpdates = data.map(article => Object.assign({}, { id: article.id, changes: article }));
this.store.dispatch(fromActions.UpdateArticles({ payload: { articles: allUpdates } }));
}
removeArticle(articleId: string) {
this.store.dispatch(fromActions.RemoveArticle({ payload: { id: articleId } }));
}
removeArticles(articleIds: string[]) {
this.store.dispatch(fromActions.RemoveArticles({ payload: { ids: articleIds } }));
}
clearAllArticles() {
this.store.dispatch(fromActions.ClearArticles());
}
loadAllArticles() {
this.task = 'all';
}
selectArticleById() {
this.store.dispatch(fromActions.SelectArticle({ payload: { articleId: this.articleId } }));
}
}
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-article></app-article>
`
})
export class AppComponent {
}
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { AppComponent } from './app.component';
import { ArticleComponent } from './components/article.component';
import { reducers } from './reducers';
import { ArticleEffects } from './effects/article.effects';
import { ArticleService } from './services/article.service';
//For InMemory testing
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { TestData } from './test-data';
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
FormsModule,
HttpClientModule,
StoreModule.forRoot(reducers),
EffectsModule.forRoot([ArticleEffects]),ty
InMemoryWebApiModule.forRoot(TestData)
],
declarations: [
AppComponent,
ArticleComponent
],
providers: [
ArticleService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
styles.css
input {
width: 230px;
background-color: #dfdfdf;
font-size:16px;
}
input[type="checkbox"] {
width: 20px;
}
button {
background-color: #008CBA;
color: white;
}
ul li {
background: #f2f4f7;
margin: 5px;
}
12. Run Application
To run the application, find the following steps.
1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Install NgRx/Store, NgRx/Entity, NgRx/Effects and angular-in-memory-web-api using NPM command.
4. Run ng serve command.
5. Now access the URL http://localhost:4200
When we click on Update Article button and then click on Update More Article, we will get following print screen.
13. References
Entity Adapter
Entity Interfaces
@ngrx/entity
angular-in-memory-web-api
14. Download Source Code
Thank you for reading until the end. Please consider following the writer and this publication. Visit Stackademic to find out more about how we are democratizing free programming education around the world.