Learn Startup - Build a successful business and change the world

<strong style="color: rgb(118, 131, 143);">Build a Basic CRUD App with Angular and Node</strong>

Build a Basic CRUD App with Angular and Node

In recent years, single page applications (SPAs) have become more and more popular. A SPA is a website that consists of just one page. That lone page acts as a container for a JavaScript application. The JavaScript is responsible for obtaining the content and rendering it within the container. The content is typically obtained from a web service and RESTful APIs have become the go-to choice in many situations. The part of the application making up the SPA is commonly known as the client or front-end, while the part responsible for the REST API is known as the server or back-end. In this tutorial, you will be developing a simple Angular single page app with a REST backend, based on Node and Express.

You’ll be using Angular as it follows the MVC pattern and cleanly separates the View from the Models. It is straightforward to create HTML templates that are dynamically filled with data and automatically updated whenever the data changes. I have come to love this framework because it is very powerful, has a huge community and excellent documentation.

--ADVERTISEMENT--

For the server, you will be using Node with Express. Express is a framework that makes it easy to create REST APIs by allowing to define code that runs for different requests on the server. Additional services can be plugged in globally, or depending on the request. There are a number of frameworks that build on top of Express and automate the task of turning your database models into an API. This tutorial will not make use of any of these in order to keep this focused.

Angular encourages the use of TypeScript. TypeScript adds typing information to JavaScript and, in my opinion, is the future of developing large scale applications in JavaScript. For this reason, you will be developing both client and server using TypeScript.

Here are the libraries you’ll be using for the client and the server:

  • Angular: The framework used to build the client application
  • Okta for Authorisation: A plugin that manages single sign-on authorization using Okta, both on the client and the server
  • Angular Material: An angular plugin that provides out-of-the-box Material Design
  • Node: The actual server running the JavaScript code
  • Express: A routing library for responding to server requests and building REST APIs
  • TypeORM: A database ORM library for TypeScript
Start Your Basic Angular Client Application

Let’s get started by implementing a basic client using Angular. The goal is to develop a product catalog which lets you manage products, their prices, and their stock levels. At the end of this section, you will have a simple application consisting of a top bar and two views, Home and Products. The Products view will not yet have any content and nothing will be password protected. This will be covered in the following sections.

To start you will need to install Angular. I will assume that you already have Node installed on your system and you can use the npm command. Type the following command into a terminal.

npm install -g @angular/[email protected]

Depending on your system, you might need to run this command using sudo because it will install the package globally. The angular-cli package provides the ng command that is used to manage Angular applications. Once installed go to a directory of your choice and create your first Angular application using the following command.

ng new MyAngularClient

Using Angular 7, this will prompt you with two queries. The first asks you if you want to include routing. Answer yes to this. The second query relates to the type of style sheets you want to use. Leave this at the default CSS.

ng new will create a new directory called MyAngularClient and populate it with an application skeleton. Let’s take a bit of time to look at some of the files that the previous command created. At the src directory of the app, you will find a file index.html that is the main page of the application. It doesn’t contain much and simply plays the role of a container. You will also see a style.css file. This contains the global style sheet that is applied throughout the application. If you browse through the folders you might notice a directory src/app containing five files.

app-routing.module.ts
app.component.css
app.component.html
app.component.ts
app.component.spec.ts
app.module.ts

These files define the main application component that will be inserted into the index.html. Here is a short description of each of the files:

  • app.component.css file contains the style sheets of the main app component. Styles can be defined locally for each component
  • app.component.html contains the HTML template of the component
  • app.component.ts file contains the code controlling the view
  • app.module.ts defines which modules your app will use
  • app-routing.module.ts is set up to define the routes for your application
  • app.component.spec.ts contains a skeleton for unit testing the app component

I will not be covering testing in this tutorial, but in real life applications, you should make use of this feature. Before you can get started, you will need to install a few more packages. These will help you to quickly create a nicely designed responsive layout. Navigate to the base directory of the client, MyAngularClient, and type the following command.


npm i @angular/[email protected] @angular/[email protected] @angular/[email protected] @angular/[email protected]

The @angular/material and @angular/cdk libraries provide components based on Google’s Material Design, @angular/animations is used to provide smooth transitions, and @angular/flex-layout gives you the tools to make your design responsive.

Next, create the HTML template for the app component. Open src/app/app.component.html and replace the content with the following.

<mat-toolbar color="primary" class="expanded-toolbar">
<button mat-button routerLink="/">{{title}}</button>

<div fxLayout="row" fxShow="false" fxShow.gt-sm>
<button mat-button routerLink="/"><mat-icon>home</mat-icon></button>
<button mat-button routerLink="/products">Products</button>
<button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button>
<button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button>
</div>
<button mat-button [mat-menu-trigger-for]="menu" fxHide="false" fxHide.gt-sm>
<mat-icon>menu</mat-icon>
</button>
</mat-toolbar>
<mat-menu x-position="before" #menu="matMenu">
<button mat-menu-item routerLink="/"><mat-icon>home</mat-icon> Home</button>
<button mat-menu-item routerLink="/products">Products</button>;
<button mat-menu-item *ngIf="!isAuthenticated" (click)="login()"> Login </button>
<button mat-menu-item *ngIf="isAuthenticated" (click)="logout()"> Logout </button>
</mat-menu>
<router-outlet></router-outlet>

The mat-toolbar contains the material design toolbar, whereas router-outlet is the container that will be filled by the router. The app.component.ts file should be edited to contain the following.

import { Component } from '@angular/core';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public title = 'My Angular App';
public isAuthenticated: boolean;

constructor() {
this.isAuthenticated = false;
}

login() {
}

logout() {
}
}

This is the controller for the app component. You can see that it contains a property called isAuthenticatedtogether with two methods login and logout. At the moment these don’t do anything. They will be implemented in the next section which covers user authentication with Okta. Now define all the modules you will be using. Replace the contents of app.module.ts with the code below:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FlexLayoutModule } from '@angular/flex-layout';
import {
MatButtonModule,
MatDividerModule,
MatIconModule,
MatMenuModule,
MatProgressSpinnerModule,
MatTableModule,
MatToolbarModule
} from '@angular/material';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent
],
imports: [
AppRoutingModule,
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
FlexLayoutModule,
MatToolbarModule,
MatMenuModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatDividerModule,
MatProgressSpinnerModule,
FormsModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Notice all the material design modules. The @angular/material library requires you to import a module for each type of component you wish to use in your app. Starting with Angular 7, the default application skeleton contains a separate file called app-routing.module.ts. Edit this to declare the following routes.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ProductsComponent } from './products/products.component';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'products',
component: ProductsComponent
}
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

This defines two routes corresponding to the root path and to the products path. It also attaches the HomeComponent and the ProductsComponent to these routes. Create these components now. In the base directory of the Angular client, type the following commands.

ng generate component Products
ng generate component Home

This creates htmlcssts, and spec.ts files for each component. It also updates app.module.ts to declare the new components. Open up home.component.html in the src/app/home directory and paste the following content.

<div class="hero">
<div>
<h1>Hello World</h1>
<p class="lead">This is the homepage of your Angular app</p>
</div>
</div>

Include some styling in the home.component.css file too.

.hero {
text-align: center;
height: 90vh;
display: flex;
flex-direction: column;
justify-content: center;
font-family: sans-serif;
}

Leave the ProductsComponent empty for now. This will be implemented once you have created the back-end REST server and are able to fill it with some data. To make everything look beautiful only two little tasks remain. Copy the following styles into src/style.css

@import "[email protected]/material/prebuilt-themes/deeppurple-amber.css";

body {
margin: 0;
font-family: sans-serif;
}

.expanded-toolbar {
justify-content: space-between;
}

h1 {
text-align: center;
}

Finally, in order to render the Material Design Icons, add one line inside the <head> tags of the index.html file.

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

You are now ready to fire up the Angular server and see what you have achieved so far. In the base directory of the client app, type the following command.

ng serve

Then open your browser and navigate to http://localhost:4200.

Add Authentication to Your Node + Angular App

If you have ever developed web applications from scratch you will know how much work is involved just to allow users to register, verify, log on and log out of your application. Using Okta this process can be greatly simplified. To start off, you will need a developer account with Okta.


In your browser, navigate to developer.okta.com and click on Create Free Account and enter your details.


Once you are done you will be taken to your developer dashboard. Click on the Add Application button to create a new application.

Start by creating a new single page application. Choose Single Page App and click Next.

On the next page, you will need to edit the default settings. Make sure that the port number is 4200. This is the default port for Angular applications.

That’s it. You should now see a Client ID which you will need to paste into your TypeScript code.

To implement authentication into the client, install the Okta library for Angular.

npm install @okta/[email protected] --save-exact

In app.module.ts import the OktaAuthModule.

import { OktaAuthModule } from '@okta/okta-angular';

In the list of imports of the app module, add:

OktaAuthModule.initAuth({
issuer: 'https://{yourOktaDomain}/oauth2/default',
redirectUri: 'http://localhost:4200/implicit/callback',
clientId: '{YourClientId}'
})

Here yourOktaDomain should be replaced by the development domain you see in your browser when you navigate to your Okta dashboard. YourClientId has to be replaced by the client ID that you obtained when registering your application. The code above makes the Okta Authentication Module available in your application. Use it in app.component.ts, and import the service.

import { OktaAuthService } from '@okta/okta-angular';

Modify the constructor to inject the service and subscribe to it.

constructor(public oktaAuth: OktaAuthService) {
this.oktaAuth.$authenticationState.subscribe(
(isAuthenticated: boolean) => this.isAuthenticated = isAuthenticated
);
}

Now, any changes in the authentication status will be reflected in the isAuthenticated property. You will still need to initialize it when the component is loaded. Create a ngOnInit method and add implements OnInit to your class definition

import { Component, OnInit } from '@angular/core';
...
export class AppComponent implements OnInit {
...
async ngOnInit() {
this.isAuthenticated = await this.oktaAuth.isAuthenticated();
}
}

Finally, implement the login and logout method to react to the user interface and log the user in or out.

login() {
this.oktaAuth.loginRedirect();
}

logout() {
this.oktaAuth.logout('/');
}

In the routing module, you need to register the route that will be used for the login request. Open app-routing.module.ts and import OktaCallbackComponent and OktaAuthGuard.

import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';

Add another route to the routes array.

{
path: 'implicit/callback',
component: OktaCallbackComponent
}

This will allow the user to log in using the Login button. To protect the Products route from unauthorized access, add the following line to the products route.

{
path: 'products',
component: ProductsComponent,
canActivate: [OktaAuthGuard]
}

That’s all there is to it. Now, when a user tries to access the Products view, they will be redirected to the Okta login page. Once logged on, the user will be redirected back to the Products view.

Implement a Node REST API

The next step is to implement a server based on Node and Express that will store product information. This will use a number of smaller libraries to make your life easier. To develop in TypeScript, you’ll need typescript and tsc. For the database abstraction layer, you will be using TypeORM. This is a convenient library that injects behavior into TypeScript classes and turns them into database models. Create a new directory to contain your server application, then run the following command in it.

npm init

Answer all the questions, then run:

npm install --save-exact [email protected] @types/[email protected] @okta/[email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] @types/[email protected]

I will not cover all these libraries in detail, but you will see that @okta/jwt-verifier is used to verify JSON Web Tokens and authenticate them.

In order to make TypeScript work, create a file tsconfig.json and paste in the following content.

{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "dist",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}

The first step in creating the server is to create a database model for the product. Using TypeORM this is straightforward. Create a subdirectory src and inside that create a file model.ts. Paste the following content.

import {Entity, PrimaryGeneratedColumn, Column, createConnection, Connection, Repository} from 'typeorm';

@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column()
sku: string;

@Column('text')
description: string;

@Column()
price: number;

@Column()
stock: number;
}

The TypeORM annotations turn the class definition into a database model. I love the TypeORM project because of its ease of use and the wide variety of SQL and NoSQL database connectors that are supported. I suggest that you check out the documentation at https://github.com/typeorm/typeorm.

You will also need to get access to a repository of product. Also in the model.ts file add the following.

let connection:Connection;

export async function getProductRepository(): Promise<Repository<Product>> {
if (connection===undefined) {
connection = await createConnection({
type: 'sqlite',
database: 'myangularapp',
synchronize: true,
entities: [
Product
],
});
}
return connection.getRepository(Product);
}

Note that this uses SQLite for simplicity here. In a real-world scenario, you should replace this with a database connector of your choice.

Next, create a file called product.ts. This file will contain the logic for all the routes for the CRUD operations on Products.

import { NextFunction, Request, Response, Router } from 'express';
import { getProductRepository, Product } from './model';

export const router: Router = Router();

router.get('/product', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
const allProducts = await repository.find();
res.send(allProducts);
}
catch (err) {
return next(err);
}
});

router.get('/product/:id', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
const product = await repository.find({id: req.params.id});
res.send(product);
}
catch (err) {
return next(err);
}
});

router.post('/product', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
const product = new Product();
product.name = req.body.name;
product.sku = req.body.sku;
product.description = req.body.description;
product.price = Number.parseFloat(req.body.price);
product.stock = Number.parseInt(req.body.stock);

<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> repository.save(product);
res.send(result);

}
catch (err) {
return next(err);
}
});

router.post('/product/:id', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
const product = await repository.findOne({id: req.params.id});
product.name = req.body.name;
product.sku = req.body.sku;
product.description = req.body.description;
product.price = Number.parseFloat(req.body.price);
product.stock = Number.parseInt(req.body.stock);

<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> repository.save(product);
res.send(result);

}
catch (err) {
return next(err);
}
});

router.delete('/product/:id', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
await repository.delete({id: req.params.id});
res.send('OK');
}
catch (err) {
return next(err);
}
});

This file is somewhat lengthy but does not contain anything surprising. Product objects are created and saved into or deleted from the database.

Let’s turn our attention to authentication again. You will want to make sure that only authenticated users can access the service. Create a file called auth.ts and paste the following.

import { Request, Response, NextFunction} from 'express';

const OktaJwtVerifier = require('@okta/jwt-verifier');

const oktaJwtVerifier = new OktaJwtVerifier({
clientId: '{YourClientId}',
issuer: 'https://{yourOktaDomain}/oauth2/default'
});

export async function oktaAuth(req:Request, res:Response, next:NextFunction) {
try {
const token = (req as any).token;
if (!token) {
return res.status(401).send('Not Authorised');
}
const jwt = await oktaJwtVerifier.verifyAccessToken(token);
req.user = {
uid: jwt.claims.uid,
email: jwt.claims.sub
};
next();
}
catch (err) {
return res.status(401).send(err.message);
}
}

Just as in the client application yourOktaDomain should be replaced by the development domain and YourClientIdhas to be replaced by your application client ID. The oktaJwtVerifier instance takes a JWT token and authenticates it. If successful, the user id and email will be stored in req.user. Otherwise, the server will respond with a 401 status code. The final piece to complete the server is the main entry point that actually starts the server and registers the middleware you have defined so far. Create a file server.ts with the following content.

import * as express from 'express';
import * as cors from 'cors';
import * as bodyParser from 'body-parser';
const bearerToken = require('express-bearer-token');
import {router as productRouter} from './product'
import {oktaAuth} from './auth'

const app = express()
.use(cors())
.use(bodyParser.json())
.use(bearerToken())
.use(oktaAuth)
.use(productRouter);

app.listen(4201, (err) => {
if (err) {
return console.log(err);
}

return console.log('My Node App listening on port 4201');
});

To compile the TypeScript run the command

npx tsc

Then, if you want to start the server, simply run:

node dist/server.js

Finish Your Angular Client

Now that the server is complete let’s finish off the client. The first step is to create a class that contains the Product data. This class is similar to the Product class in the server application but without the TypeORM annotations. It will be contained in a file called product.ts.

export class Product {
id?: string;
name: string;
sku: string;
description: string;
price: number;
stock: number;
}

Save this file in the same directory as the products component. It is best to encapsulate the access to the REST API in a separate service. Create a Products service by running the command below.

ng generate service products/Products

This will create a file called product.service.ts in the src/app/products directory. Fill it with the following content.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { OktaAuthService } from '@okta/okta-angular';
import { Product } from './product';

const baseUrl = 'http://localhost:4201';

@Injectable({
providedIn: 'root'
})
export class ProductsService {

constructor(public oktaAuth: OktaAuthService, private http: HttpClient) {
}

private async request(method: string, url: string, data?: any) {
const token = await this.oktaAuth.getAccessToken();

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'request '</span> + <span class="hljs-built_in">JSON</span>.stringify(data));
<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">this</span>.http.request(method, url, {
  <span class="hljs-attr">body</span>: data,
  <span class="hljs-attr">responseType</span>: <span class="hljs-string">'json'</span>,
  <span class="hljs-attr">observe</span>: <span class="hljs-string">'body'</span>,
  <span class="hljs-attr">headers</span>: {
    <span class="hljs-attr">Authorization</span>: <span class="hljs-string">`Bearer <span class="hljs-subst">${token}</span>`</span>
  }
});
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>&lt;any&gt;<span class="hljs-function">(<span class="hljs-params">(resolve, reject</span>) =&gt;</span> {
  result.subscribe(resolve <span class="hljs-keyword">as</span> any, reject <span class="hljs-keyword">as</span> any);
});

}

getProducts() {
return this.request('get', <span class="hljs-subst">${baseUrl}</span>/product);
}

getProduct(id: string) {
return this.request('get', <span class="hljs-subst">${baseUrl}</span>/product/<span class="hljs-subst">${id}</span>);
}

createProduct(product: Product) {
console.log('createProduct ' + JSON.stringify(product));
return this.request('post', <span class="hljs-subst">${baseUrl}</span>/product, product);
}

updateProduct(product: Product) {
console.log('updateProduct ' + JSON.stringify(product));
return this.request('post', <span class="hljs-subst">${baseUrl}</span>/product/<span class="hljs-subst">${product.id}</span>, product);
}

deleteProduct(id: string) {
return this.request('delete', <span class="hljs-subst">${baseUrl}</span>/product/<span class="hljs-subst">${id}</span>);
}
}

The ProductsService contains one public method for each route of the REST API. The HTTP request is encapsulated in a separate method. Note how the request always contains a Bearer token obtained from the OktaAuthService. This is the token used by the server to authenticate the user.

Now the ProductsComponent can be implemented. The following code will do the trick.

import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material';
import { ProductsService } from './products.service';
import { Product } from './product';

@Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
displayedColumns: string[] = ['name', 'sku', 'description', 'price', 'stock', 'edit', 'delete'];
dataSource = new MatTableDataSource<any>();

selectedProduct: Product = new Product();
loading = false;

constructor(public productService: ProductsService) {
}

ngOnInit() {
this.refresh();
}

async refresh() {
this.loading = true;
const data = await this.productService.getProducts();
this.dataSource.data = data;
this.loading = false;
}

async updateProduct() {
if (this.selectedProduct.id !== undefined) {
await this.productService.updateProduct(this.selectedProduct);
} else {
await this.productService.createProduct(this.selectedProduct);
}
this.selectedProduct = new Product();
await this.refresh();
}

editProduct(product: Product) {
this.selectedProduct = product;
}

clearProduct() {
this.selectedProduct = new Product();
}

async deleteProduct(product: Product) {
this.loading = true;
if (confirm(Are you sure you want to delete the product <span class="hljs-subst">${product.name}</span>. This cannot be undone.)) {
this.productService.deleteProduct(product.id);
}
await this.refresh();
}
}

The layout, in products.component.html, showing the product consists of two parts. The first part uses a mat-table component to display a list of products. The second part shows a form in which the user can edit a new or existing product.

<h1 class="h1">Product Inventory</h1>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="space-between stretch" class="products">
<table mat-table fxFlex="100%" fxFlex.gt-sm="66%" [dataSource]="dataSource" class="mat-elevation-z1">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name</th>
<td mat-cell *matCellDef="let product"> {{product.name}}</td>
</ng-container>
<ng-container matColumnDef="sku">
<th mat-header-cell *matHeaderCellDef> SKU</th>
<td mat-cell *matCellDef="let product"> {{product.sku}}</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef> Description</th>
<td mat-cell *matCellDef="let product"> {{product.description}}</td>
</ng-container>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef> Price</th>
<td mat-cell *matCellDef="let product"> {{product.price}}</td>
</ng-container>
<ng-container matColumnDef="stock">
<th mat-header-cell *matHeaderCellDef> Stock Level</th>
<td mat-cell *matCellDef="let product"> {{product.stock}}</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let product">
<button mat-icon-button (click)="editProduct(product)">
<mat-icon>edit</mat-icon>
</button>
</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let product">
<button mat-icon-button (click)="deleteProduct(product)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-divider fxShow="false" fxShow.gt-sm [vertical]="true"></mat-divider>
<div>
<h2>Selected Product</h2>
<label>Name
<input type="text" [(ngModel)]="selectedProduct.name">
</label>
<label>SKU
<input type="text" [(ngModel)]="selectedProduct.sku">
</label>
<label>Description
<input type="text" [(ngModel)]="selectedProduct.description">
</label>
<label>Price
<input type="text" [(ngModel)]="selectedProduct.price">
</label>
<label>Stock Level
<input type="text" [(ngModel)]="selectedProduct.stock">
</label>
<button mat-flat-button color="primary" (click)="updateProduct()">{{(selectedProduct.id!==undefined)?'Update':'Create'}}</button>
<button mat-flat-button color="primary" (click)="clearProduct()">Clear</button>
</div>
<div class="loading" *ngIf="loading">
<mat-spinner></mat-spinner>
</div>
</div>

Finally, add a bit of styling in products.component.css to the layout.

.products {
padding: 2rem;
}

label, input {
display: block;
}

label {
margin-bottom: 1rem;
}

.loading {
position: absolute;
display: flex;
justify-content: center;
align-content: center;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
}

When all is done, you can start the client and the server up and test your application. Just to repeat, in the directory containing the server, run:

node dist/server.js

And in the client directory, run:

ng serve

Your application should look somewhat like the following


Angular 8 Node & Express JS File Upload

Angular 8 Node & Express JS File Upload

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

In this Angular 8 and Node.js tutorial, we are going to look at how to upload files on the Node server. To create Angular image upload component, we will be using Angular 8 front-end framework along with ng2-file-upload NPM package; It’s an easy to use Angular directives for uploading the files.

We are also going to take the help of Node.js to create the backend server for Image or File uploading demo. Initially, we’ll set up an Angular 8 web app from scratch using Angular CLI. You must have Node.js and Angular CLI installed in your system.

We’ll create the local server using Node.js and multer middleware. Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. Once we are done setting up front-end and backend for our File uploading demo then, we’ll understand step by step how to configure file uploading in Angular 8 app using Node server.

Prerequisite

In order to show you Angular 8 File upload demo, you must have Node.js and Angular CLI installed in your system. If not then check out this tutorial: Set up Node JS

Run following command to install Angular CLI:

npm install @angular/cli -g

Install Angular 8 App

Run command to install Angular 8 project:

ng new angular-node-file-upload

# ? Would you like to add Angular routing? No
# ? Which stylesheet format would you like to use? CSS
cd angular-node-file-upload

Show Alert Messages When File Uploaded

We are going to install and configure ngx-toastr an NPM package which helps in showing the alert message when the file is uploaded on the node server.

npm install ngx-toastr --save

The ngx-toastr NPM module requires @angular/animations dependency:

npm install @angular/animations --save

Then, add the ngx-toastr CSS in angular.json file:

"styles": [
    "src/styles.css",
    "node_modules/ngx-toastr/toastr.css"
]

Import BrowserAnimationsModule and ToastrModule in app.module.ts file:

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
 
@NgModule({
  imports: [
    CommonModule,
    BrowserAnimationsModule, // required animations module
    ToastrModule.forRoot() // ToastrModule added
  ]
})

export class AppModule { }

Install & Configure ng-file-upload Directive

In this step, we’ll Install and configure ng-file-upload library in Angular 8 app. Run command to install ng-file-upload library.

npm install ng2-file-upload

Once the ng2-file-upload directive is installed, then import the FileSelectDirective and FormsModule in app.module.ts. We need FormsModule service so that we can create the file uploading component in Angular.

import { FileSelectDirective } from 'ng2-file-upload';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    FileSelectDirective
  ],
  imports: [
    FormsModule
  ]
})

export class AppModule { }

Setting Up Node Backend for File Upload Demo

To upload the file on the server, we need to set up a separate backend. In this tutorial, we will be using Node & Express js to create server locally along with multer, express js, body-parser, and dotenv libraries.

Run command to create backend folder in Angular app’s root directory:

mkdir backend && cd backend

In the next step, create a specific package.json file.

npm init

Run command to install required dependencies:

npm install express cors body-parser multer dotenv --save

In order to get rid from starting the server again and again, install nodemon NPM package. Use –-save-dev along with the npm command to register in the devDependencies array. It will make it available for development purpose only.

npm install nodemon --save-dev

Have a look at final pacakge.json file for file upload demo backend:

{
  "name": "angular-node-file-upload",
  "version": "1.0.0",
  "description": "Angualr 8 file upload demo app",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Digamber Rawat",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^8.0.0",
    "express": "^4.17.1",
    "multer": "^1.4.1"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  }
}

Create a file by the name of server.js inside backend folder:

Configure Server.js

To configure our backend we need to create a server.js file. In this file we’ll keep our backend server’s settings.

touch server.js

Now, paste the following code in backend > server.js file:

const express = require('express'),
  path = require('path'),
  cors = require('cors'),
  multer = require('multer'),
  bodyParser = require('body-parser');

// File upload settings  
const PATH = './uploads';

let storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, PATH);
  },
  filename: (req, file, cb) => {
    cb(null, file.fieldname + '-' + Date.now())
  }
});

let upload = multer({
  storage: storage
});

// Express settings
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

app.get('/api', function (req, res) {
  res.end('File catcher');
});

// POST File
app.post('/api/upload', upload.single('image'), function (req, res) {
  if (!req.file) {
    console.log("No file is available!");
    return res.send({
      success: false
    });

  } else {
    console.log('File is available!');
    return res.send({
      success: true
    })
  }
});

// Create PORT
const PORT = process.env.PORT || 8080;
const server = app.listen(PORT, () => {
  console.log('Connected to port ' + PORT)
})

// Find 404 and hand over to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  console.error(err.message);
  if (!err.statusCode) err.statusCode = 500;
  res.status(err.statusCode).send(err.message);
});

Now, while staying in the backend folder run the below command to start the backend server:

nodemon server.js

If everything goes fine then you’ll get the following output:

[nodemon] 1.19.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
Connected to port 8080

Create Angular 8 File Upload Component

In this last step, we are going to create a file upload component in Angular 8 app using Express js API.

Get into the app.component.ts file and include the following code:

import { Component, OnInit } from '@angular/core';
import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
import { ToastrService } from 'ngx-toastr';

const URL = 'http://localhost:8080/api/upload';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  public uploader: FileUploader = new FileUploader({
    url: URL,
    itemAlias: 'image'
  });

  constructor(private toastr: ToastrService) { }

  ngOnInit() {
    this.uploader.onAfterAddingFile = (file) => {
      file.withCredentials = false;
    };
    this.uploader.onCompleteItem = (item: any, status: any) => {
      console.log('Uploaded File Details:', item);
      this.toastr.success('File successfully uploaded!');
    };
  }

}

Go to app.component.html file and add the given below code:

<div class="wrapper">
  <h2>Angular Image Upload Demo</h2>

  <div class="file-upload">
    <input type="file" name="image" ng2FileSelect [uploader]="uploader" accept="image/x-png,image/gif,image/jpeg" />
    <button type="button" (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">
      Upload
    </button>
  </div>

</div>

Now, It’s time to start the Angular 8 app to check out the File upload demo in the browser. Run the following command:

ng serve --open

Make sure your NODE server must be running to manage the backend.

When you upload the image from front-end you’ll see your image files are saving inside the backend > uploads folder.

Conclusion

In this Angular 8 tutorial, we barely scratched the surface related to file uploading in a Node application. There are various other methods available on the internet through which you can achieve file uploading task quickly. However, this tutorial is suitable for beginners developers. I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others.

How to Implement Server Side Pagination with Angular 8 and Node.js

How to Implement Server Side Pagination with Angular 8 and Node.js

A simple example of how to implement server-side pagination in Angular 8 with a Node.js backend API.

This is a simple example of how to implement server-side pagination in Angular 8 with a Node.js backend API.

The example contains a hard coded array of 150 objects split into 30 pages (5 items per page) to demonstrate how the pagination logic works. Styling of the example is done with Bootstap 4.

The tutorial code is available on GitHub at https://github.com/cornflourblue/angular-8-node-server-side-pagination.

Here it is in action (may take a few seconds for the container to startup):

Running the Angular + Node Pagination Example Locally
  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the tutorial project source code from https://github.com/cornflourblue/angular-8-node-server-side-pagination.
  3. Install required npm packages of the backend Node API by running the npm install command in the /server folder.
  4. Start the backend Node API by running npm start in the /server folder, this will start the API on the URL http://localhost:4000.
  5. Install required npm packages of the frontend Angular app by running the npm install command in the /client folder.
  6. Start the Angular frontend app by running npm start in the /client folder, this will build the app with webpack and automatically launch it in a browser on the URL http://localhost:8080.
Server-Side (Node.js) Pagination Logic

Below is the code for the paged items route (/api/items) in the node server file (/server/server.js) in the example, it creates a hardcoded list of 150 items to be paged, in a real application you would replace this with real data (e.g. from a database). The route accepts an optional page parameter in the url query string, if the parameter isn't set it defaults to the first page.

The paginate() function is from the jw-paginate package and accepts the following parameters:

  • totalItems (required) - the total number of items to be paged
  • currentPage (optional) - the current active page, defaults to the first page
  • pageSize (optional) - the number of items per page, defaults to 10
  • maxPages (optional) - the maximum number of page navigation links to display, defaults to 10

The output of the paginate function is a pager object containing all the information needed to get the current pageOfItems out of the items array, and to display the pagination controls in the Angular frontend, including:

  • startIndex - the index of the first item of the current page (e.g. 0)
  • endIndex - the index of the last item of the current page (e.g. 9)
  • pages - the array of page numbers to display (e.g. [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ])
  • currentPage - the current active page (e.g. 1)
  • totalPages - the total number of pages (e.g. 30)

I've set the pageSize to 5 in the CodeSandbox example above so the pagination links aren't hidden below the terminal console when the container starts up. In the code on GitHub I didn't set the page size so the default 10 items are displayed per page in that version.

The current pageOfItems is extracted from the items array using the startIndex and endIndex from the pager object. The route then returns the pager object and current page of items in a JSON response.

// paged items route
app.get('/api/items', (req, res, next) => {
    // example array of 150 items to be paged
    const items = [...Array(150).keys()].map(i => ({ id: (i + 1), name: 'Item ' + (i + 1) }));

    // get page from query params or default to first page
    const page = parseInt(req.query.page) || 1;

    // get pager object for specified page
    const pageSize = 5;
    const pager = paginate(items.length, page, pageSize);

    // get page of items from items array
    const pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);

    // return pager object and current page of items
    return res.json({ pager, pageOfItems });
});

Client-Side (Angular 8) Pagination Components

Since the pagination logic is handled on the server, the only thing the Angular client needs to do is fetch the pager information and current page of items from the backend, and display them to the user.

Angular Home Component

Below is the Angular home component (/client/src/app/home/home.component.ts) from the example. The loadPage() method fetches the pager object and pageOfItems for the specified page from the backend API with an HTTP request, and the ngOnInit() gets the page parameter in the route query params or defaults to the first page if the query param is empty.

By subscribing to the route.queryParams observable, the home component is notified whenever the page changes so it can load the new page.

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent implements OnInit {
    pager = {};
    pageOfItems = [];

    constructor(
        private http: HttpClient,
        private route: ActivatedRoute
    ) { }

    ngOnInit() {
        // load page based on 'page' query param or default to 1
        this.route.queryParams.subscribe(x => this.loadPage(x.page || 1));
    }

    private loadPage(page) {
        // get page of items from api
        this.http.get(`/api/items?page=${page}`).subscribe(x => {
            this.pager = x.pager;
            this.pageOfItems = x.pageOfItems;
        });
    }
}

Angular Home Component Template

The home component template (/client/src/app/home/home.component.html) renders the current page of items as a list of divs with the *ngFor directive, and renders the pagination controls using the data from the pager object. Each pagination link sets the page query parameter in the url with the routerLink directive and [queryParams] property.

The CSS classes used are all part of Bootstrap 4.3, for more info see https://getbootstrap.com/docs/4.3/getting-started/introduction/.


    ### Angular 8 + Node - Server Side Pagination Example

    
        {{item.name}}

    
    
        
            
                First
            
            
                Previous
            
            
                {{page}}
            
            
                Next
            
            
                Last
            
        
    


I hope this tutorial will surely help and you if you liked this tutorial, please consider sharing it with others.

Thanks For Visiting, Keep Visiting.

Difference between Node.js and React.js and Angular.js

AngularJS turned the whole definition of web development through its existence, what was lacking in previous methods of development was inappropriate structure. It merges use of JavaScript with MVC and HTML with DOM. ReactJS is a JavaScript library and has a view, but lacks model &amp; controller components. React introduced the concept of virtual DOM which is regarded as one of the greatest advantages of React in comparison of AngularJS. Node.js is a JavaScript-based platform used for developing real-time network applications like a chat software.

AngularJS turned the whole definition of web development through its existence, what was lacking in previous methods of development was inappropriate structure. It merges use of JavaScript with MVC and HTML with DOM. ReactJS is a JavaScript library and has a view, but lacks model & controller components. React introduced the concept of virtual DOM which is regarded as one of the greatest advantages of React in comparison of AngularJS. Node.js is a JavaScript-based platform used for developing real-time network applications like a chat software.