Angular: Build a CRUD Application With NgRx

Build a simple course management system in Angular

Sarindu Udagepala
Jan 21 · 12 min read

Introduction

In my previous post, I explained the fundamental concepts of NgRx and how they fit in together. Now, it’s time to build a mini-CRUD application with NgRx as the state management system.

If you are brand new to the world of NgRx, I suggest you check out my previous post before attempting this tutorial. I won’t be going into detail about the underlying NgRx concepts in this article.


Project Details

Throughout this article, we will be building a simple course management system. As shown below, you will be able to perform all the CRUD operations on the course entity via this simple web application.

As the following figure illustrates, our application will consist of two primary modules, namely App and Course. The course module, in turn, will have two custom components, namely Course List and Create Course.

In general, an Angular application interacts with a REST API to perform CRUD operations on data.

Therefore, I implemented a simple REST API in Spring Boot that exposes the below endpoints. We will use this API to connect from the Angular application and carry out data operations.

// Retrieve all courses
GET http://localhost:8080/api/courses
// Create a course
POST http://localhost:8080/api/courses
// Delete a course
DELETE http://localhost:8080/api/courses/{courseId}
// Update a course
PUT http://localhost:8080/api/courses/{courseId}

You can find the complete source code of this sample application on GitHub. Please note that I have also added the executable JAR file (course-1.0.0-SNAPSHOT.jar) of the Spring Boot application (REST API) to the same repository.


NgRx Entity at a Glance

You have already come across most of the NgRx terms that I will use in this article. For example, store, effects, actions, selectors, and reducers. In this article, I will introduce a new NgRx library called NgRx Entity (@ngrx/entity).

NgRx Entity helps us to manage various data entities in an application. For example, the Course is an entity in our application. It takes the following format.

export interface Course {id: string;name: string;description: string;}

The NgRx Entity library makes it very easy to perform different operations (add, update, remove, select) on course objects stored in the application state. Let’s see how…

The entity library provides a set of tools to make our life easier with NgRx. The first and foremost is the EntityState interface. The shape of theEntityState looks like the below.

interface EntityState<V> {
ids: string[];
entities: { [id: string]: V };
}

We have to use EntityState to declare the interface for our courses state.

import { EntityState } from '@ngrx/entity';export interface CourseState extends EntityState<Course> { }

When EntityState is used, the courses state will take the following format.

As you can see, it maintains an array of course IDs and a dictionary of course objects. There are two primary reasons we maintain a list of IDs and a dictionary of entities as opposed to just maintaining an array of entities:

  1. We want to make looking up a specific entity fast. If you wanted to just select one course from the store, using the entities dictionary is much faster than searching through an array
  2. We also want to maintain the order of the list. This is especially important if you want to keep the list sorted!

Entity adapter is another tool that goes hand-in-hand with EntityState. It provides a bunch of helper methods that make it very easy to perform various operations on data stored in EntityState.

These helper methods make reducers simple, expressive, and consistent. You can create an entity adapter in the following way.

import { createEntityAdapter } from '@ngrx/entity';
const courseAdapter = createEntityAdapter<Course>();

The following are some of the very useful methods exposed by the adapter to interact with the state.

  • addOne: Add one entity to the collection.
  • addMany: Add multiple entities to the collection.
  • addAll: Replace current collection with provided collection.
  • removeOne: Remove one entity from the collection.
  • removeMany: Remove multiple entities from the collection, by I or by the predicate.
  • removeAll: Clear entity collection.
  • updateOne: Update one entity in the collection.
  • updateMany: Update multiple entities in the collection.
  • upsertOne: Add or update one entity in the collection.
  • upsertMany: Add or update multiple entities in the collection.
  • map: Update multiple entities in the collection by defining a map function, similar to Array.map.

Setting Up the Project

  • Angular CLI: 8.0.1
  • Node: 11.6.0
  • Angular: 8.0.2
  • NgRx: 8.6.0
  • Bootstrap: 4.4.1

Step 1: Execute the below command and create a new project.

ng new angular-ngrx-example

Step 2: We will use Bootstrap to add styles to our application. You can install Bootstrap with the below command.

npm install bootstrap --save

Step 3: Import Bootstrap by updating the angular.json file as shown below.

Step 4: Install NgRx dependencies.

npm install @ngrx/{store,effects,entity,store-devtools,schematics} --save

Adding NgRx Support to Root Module

Execute the below schematics command to generate the initial state management and register it within the app.module.ts.

ng generate @ngrx/schematics:store State --root --statePath store/reducers --module app.module.ts

After the above command, your project folder structure should look like the below.

Following is the content of the index.ts file. Please note that I made a couple of minor changes to the auto-generated file. For example, I changed the State interface to AppState for the sake of clarity.

The NgRx schematics command will also update the app.module.ts file. Following is the updated content of this file.


Creating and Setting Up the “Course” Feature Module

As stated earlier, our application consists of two major modules, namely App and Course. Now is the time to create the Course module with the below command.

ng generate module course

The aforementioned command will create a sub-folder named course directly under the app folder. In addition, a new file named course.module.ts will be created and placed under the app/course folder.

Following is the initial version of course.module.ts file. Note that this file will be altered downstream to add NgRx support, declare components, and declare service providers.

As the next step, you have to define the model interface that represents the Course entity. Create a file called course.model.ts and place it under the app/course/model folder. The content of this file should be as follows.

Service is used to interact with the REST API and perform data operations. In order to define the service class, create a file named course.service.ts and place it under the app/course/services folder.

The content of this file should be as follows.

As you can see, it has methods to retrieve, create, update, and delete Course entities via the REST API. Once the service class is defined, you have to register it in the course.module.ts file as shown below.

The following figure illustrates the folder structure of our application at this point.


Adding NgRx Artifacts to “Course” Module

As the next step, you have to define actions, reducers, effects, and selectors and attach to the course module. These artifacts will be created inside a directory called store which in turn is located under the app/course directory.

Special notes:

  • loadCourses, createCourse, deleteCourse, and updateCourse are self-explanatory actions which are dispatched by the components. However, coursesLoaded is a special action that will be dispatched by the effect in order to inform the store that the courses were loaded successfully.
  • The updateCourse action accepts a payload of type {update: Update<Course>}. Update is an auxiliary type provided by NgRx Entity to help model partial entity updates. This type has a property id that identifies the updated entity, and another property called changes that specifies which modifications are being made to the entity.

Special notes:

  • The below code snippet defines the Course state by extending the EntityState. As we discussed before, EntityState maintains a list of IDs and a dictionary of entities. In addition to those two properties, we are here defining a custom property called coursesLoaded. This property is primarily used to indicate whether the courses have been already loaded into the state.
export interface CourseState extends EntityState<Course> {  coursesLoaded: boolean;}
  • The below code snippet creates an Entity Adapter that provides the helper functions.
export const adapter: EntityAdapter<Course> = createEntityAdapter<Course>();
  • The initial state is defined as follows. Entity adapter provides a helper function to obtain the initial state. As you can see, we are setting the coursesLoaded property to false initially.
export const initialState = adapter.getInitialState({  coursesLoaded: false});
  • The following code line exports a couple of predefined selectors provided to us by the adapter. These selectors will be used by our custom selectors. (We will look into this when we define our selectors.)
export const { selectAll, selectIds } = adapter.getSelectors();

Special notes:

  • Here, we are using the selectAll predefined selector to retrieve all the course entities as an array.
  • The areCoursesLoaded selector is used to check whether the courses have been already loaded into the state. This selector uses the coursesLoaded custom property we defined under CourseState.

Special notes:

createCourse$, deleteCourse$, and updateCourse$ effects are self-explanatory. They simply invoke the corresponding REST endpoint and perform the operation.

These effects do not map the incoming action to a new action type, which is why {dispatch: false} config is used.

However, loadCourses$ has a special behavior. It accepts the actions of type loadCourses and once the courses are retrieved via the REST API, it maps the response to a new action type called coursesLoaded.

The retrieved list of courses is passed into the coursesLoaded action.

After the NgRx artifacts are defined, update the course.module.ts file as shown below to add the State support.

Special notes:

  • The below code line creates a dedicated slice (courses) in the application state for the course module and attaches the reducers to it.
StoreModule.forFeature('courses', courseReducer),
  • The following code line registers the effects in the course module state.
EffectsModule.forFeature([CourseEffects])

Now that that’s out of the way, your project folder structure should look like the below at this stage.


Creating Components and Defining Routes

As we discussed earlier, our application is made up of two main modules, namely App and Course. The course module consists of two components, namely courses-list and create-course.

Our next step is to create these two components and define the corresponding routes. Note that the courses-list and create-course directories will be created under the app/course/component directory.

Template: courses-list.component.html.

Component: courses-list.component.ts.

Special notes:

  • This component is responsible for facilitating the list, updating, and deleting operations.

Template: create-course.component.html.

Component: create-course.component.ts.

You have to declare the above components in the course.module.ts file.

Now is the time to define the routes and associate corresponding components with those routes. This has to be done in the app.module.ts as shown below.

Special notes:

  • CoursesListComponent uses a resolver to fetch data. A route resolver ensures that the data is available to use by the component before navigating to a particular route. In this instance, the resolver is responsible for retrieving the courses list prior to completing the navigation to /courses.

Special notes:

  • The areCoursesLoaded custom selector is used to check whether the data has already been loaded into the state.
  • The loadCourses action is dispatched only if the data is not already available in the state.
  • The operator chain will not let a value pass through to the subscriber until the coursesLoaded flag is set to true. As a result, the application will not be navigated to the /courses route until the courses are successfully loaded.

As the final step, you have to define the router outlet in the app.component.html.

At this stage, your folder structure should look like the below.


Configuring a Proxy to Access the REST API

As mentioned at the start of this article, we are using a simple REST API written in Spring Boot to connect from the Angular application.

The Spring Boot application runs on localhost:8080 whereas the Angular application runs on localhost:4200. This mismatch will cause a Cross Origin Resource Sharing (CORS) error when the Angular application tries to access the REST API. To overcome this issue we have to create a proxy.

Create a file called proxy.conf.json inside the project’s root folder (same level where package.json file exists), and add the below content to it.

In the CLI configuration file, angular.json, add the proxyConfig option to the serve target:


Running the Application

The application should be started as a two-step process. You have to first start the Spring Boot application (REST API) and then the Angular application.

The Spring Boot application is packaged into an executable JAR file with the name course-1.0.0-SNAPSHOT.jar and placed here (GitHub).

Note that you have to have Java 8 installed on your system to execute this JAR file. If Java 8 is installed, you can execute the below command and start the application.

java -jar {path_to_the_jar_file}/course-1.0.0-SNAPSHOT.jar

You should see the below log if the application started successfully.

The Angular application can be started by executing the below command.

ng serve

When the application is started successfully, navigate to http://localhost:4200/courses from your browser and you should see the below screen.


Understanding the Execution Flow

Special note: The key thing to note is that the reducer updates the state with the new data (in turn, the UI will be updated), even before the effect invokes the API and actually creates a record in the server.

Special note: Again, the reducer updates the state with the updated course data (in turn, the UI will be updated), even before the effect invokes the API and actually updates the relevant record in the server.

Special note: Similar to creating a course and updating a course, the reducer removes the relevant course information from the state (in turn, the UI will be updated), even before the effect invokes the API and remove the relevant record in the server.

Optimistic UI is a pattern that you can use to simulate the results of a state mutation and update the UI even before receiving a response from the server.

In this particular application, we are following the same pattern. As explained above, when creating, updating, and deleting a course, the state and the UI are updated even before receiving a response from the REST API.


Conclusion

The prime objective of this story was to provide a step-by-step by guide to build an NgRx-based Angular application.

As explained in the previous section, we have used the optimistic UI pattern to implement this mini system.

In my next article, I’m planning to explain how error handling can be done when using the optimistic UI pattern for NgRx-based applications.

Better Programming

Advice for programmers.

More From Medium

More from Better Programming

More from Better Programming

More from Better Programming

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade