Home  >  Angular  >  NgRx

NgRx/Entity Example

By Arvind Rai, December 23, 2021
This page will walk through 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 13.0.0
2. NgRx 13.0.0
3. Node.js 12.20.0
4. NPM 8.2.0

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.
NgRx/Entity Example

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]),
      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.
NgRx/Entity Example

13. References

Entity Adapter
Entity Interfaces
@ngrx/entity
angular-in-memory-web-api

14. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI










©2022 concretepage.com | Privacy Policy | Contact Us