Create a page menu in Angular 10 with active routing with a Bootstrap Layout

Showing the finished tutorial result of when visiting http://localhost:4200/ and http://localhost:4200/awesome-stuff and their menu links highlighted depending on what page-url you are in.

Something that wasn’t clear from me from the start when starting with Angular was how to indicate that the page is active whenever you are on it. If you are at the homepage, the home menu item should be active with bold styling or highlighted in any way, and if I go to another page, it should change and be the other page instead and so forth. Pretty straight forward. How does it work in Angular? Well it turns out it is very simple! To make things more fun we will use Bootstrap and have a simple Bootstrap layout so that we have a menu to the left and the content to the right so the menu is visible at all times.

Let us create a new project with ng new assuming you have installed Angular already. I am using Angular 10 right now. Mine is called MyMenuApp.

ng new MyMenuApp

I am going to go with routing and CSS as styling syntax.

Go into your directory and do the serve to see it is up and running. First we will use cd to go into the root app directory then type the ng serve command.

cd .\MyMenuApp\
ng serve

You should have an app up and running with sample code.

Add bootstrap

Now we will prepare some layout and styling for our application. To keep things pretty and structured we will import the bootstrap framework. By using bootstrap we can easily have the menu in a column to the left and our main content to the right. There are different ways to import the framework, but we will go with the npm install way which I believe is the easiest. Type the npm install/i command as below.

npm i bootstrap

Okay good we have bootstrap in our modules folder. Now we just need to refer to it.

Open your angular.json file and look for the “styles” property and add bootstrap.min.css.

Now my angular.json file looks like this.

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "MyMenuApp": {
      "projectType": "application",
      "schematics": {},
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/MyMenuApp",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "aot": true,
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css",
              "./node_modules/bootstrap/dist/css/bootstrap.min.css"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb",
                  "maximumError": "10kb"
                }
              ]
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "MyMenuApp:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "MyMenuApp:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "MyMenuApp:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": []
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json",
              "e2e/tsconfig.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          }
        },
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "MyMenuApp:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "MyMenuApp:serve:production"
            }
          }
        }
      }
    }},
  "defaultProject": "MyMenuApp"
}

As you can see in the above, that is where it should be placed. Now you got bootstrap in your application everywhere! Great.

Then in my styles.css I am going to add some CSS for layout purposes.

.content {
    padding: 20px;
}

body p{
  white-space: pre-wrap;
  font-size: 1.25em
}

The index.html in the src folder are we going to keep it as it is. So don’t touch it, it should look something like this.

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>MyMenuApp</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

<body>
  <app-root></app-root>
</body>

</html>

Let’s now modify the app.component.html little bit, to make sure we have a place for the menu and the content. We are going to have the menu in a col-md-2 column which should be a proper width for a menu, and then the content will be a col-md-10 to take up the remaining space. Remove everything in the app.component.html file and then write.

<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
  <a class="navbar-brand" href="http://fileidea.com">FileIdea.com</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
    aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
</nav>
<div class="container">
  <div class="row">
    <div class="col-md-2">
      <p>My menu will go here!</p>
    </div>
    <div class="col-md-10">
      <div class="content" role="main">
        <!-- Main Content -->
        <p>My content shall be here</p>
        <router-outlet></router-outlet>
      </div>
    </div>
  </div>
</div>

Now we have a nice little page that should look something like this.

Basic structure

We don’t have menu or content yet, only our text placeholders. So why don’t we create a menu component with the ng command. Go to your root project folder and type

ng generate component listMenu

We should now have a new folder containing the component. Open the list-menu.component.html and add some links! Here is where we can declare links to be active with the routing of our Angular application which is the whole purpose of this tutorial post. Replace all the sample code or text from the list-menu.component.html and replace it with the following

<div class="menu-area">
    <h3>Sections</h3>
    <div class="list-group">
        <a [routerLinkActiveOptions]="{exact: true}" routerLinkActive="active" [routerLink]="['']"
            class="list-group-item list-group-item-action">Home</a>
    </div>
</div>

Here is where the magic happens. Whenever the person is in the routing path that is matching with the routerLink path this will get an active class because of the routerLinkActive equals to active. So [routerLink]=”[”] means that if we are browsing at the home page we will get the active class for this element, this link. The routerLinkActiveOptions exact true tells us the path must match exactly.

Now we have some styling on top of this too. The active class doesn’t do anything without some declared CSS so let’s get that done as well!

Open the CSS file for the component, list-menu.component.css and add

.dropdown
{
    font-size: 20px;
}
.active
{
    z-index: 2;
    color: #fff;
    background-color: #007bff;
    border-color: #007bff;
}
.menu-area
{
    padding:20px;
}

Great we got this menu component ready to go! We can’t see it anywhere, so let’s add it to our main app component so we will always see it. Open app.component.html and add it.

<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
  <a class="navbar-brand" href="http://fileidea.com">FileIdea.com</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
    aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
</nav>
<div class="container">
  <div class="row">
    <div class="col-md-2">
      <app-list-menu></app-list-menu>
    </div>
    <div class="col-md-10">
      <div class="content" role="main">
        <!-- Main Content -->
        <p>My content shall be here</p>
        <router-outlet></router-outlet>
      </div>
    </div>
  </div>
</div>
Result after adding the component into the app component

Okay we added it and should now be able to see it in our home page. See that the link is highlighted? This is because we are http://localhost:4200/ which has the path “”. Let’s add another component which will work as another page on another routing.

ng generate component awesomeStuff

Another component as been generated! Now you can add whatever you want into the awesomeStuff component, that is not important for now. I will just add this.

<h3>Awesome stuff</h3>
<p>This is something really.</p>

Now instead of adding it into the app component directly, we will open up our routing file app-routing.module.ts. There we will add this component to a new path. Below you can see the highlighted new path I added to the routes array.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AwesomeStuffComponent } from './awesome-stuff/awesome-stuff.component';

const routes: Routes = [
  { path: 'awesome-stuff', component: AwesomeStuffComponent },
];

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

Great we got a new component for our routes! Now open up your menu component list-menu.component.html and add a new link just the same way you did last time but with the path we just added. Now my list-menu.component.html looks like this. See the highlight of what I added here.

<div class="menu-area">
    <h3>Sections</h3>
    <div class="list-group">
        <a [routerLinkActiveOptions]="{exact: true}" routerLinkActive="active" [routerLink]="['']"
            class="list-group-item list-group-item-action">Home</a>
            <a [routerLinkActiveOptions]="{exact: true}" routerLinkActive="active" [routerLink]="['awesome-stuff']"
            class="list-group-item list-group-item-action">Awesome Stuff</a>
    </div>
</div>

Awesome stuff has been linked. Now you should have a nice little app where you can visiting different links and they will get highlighted if you are at that location.

Now can also remove the html placeholder <p>My content shall be here</p> from the app.component.html page since it doesn’t serve a purpose anymore.

Now to avoid the complete whiteness when visiting http://localhost:4200/ we can add a component which will serve as the home component into our routing. Let’s add a new component that will be the welcoming content.

ng generate component home

Let’s add some html like we did before.

<h3>Home</h3>
<p>Welcome to FileIdea!</p>

Then go to routing again, and add this to the “” path.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AwesomeStuffComponent } from './awesome-stuff/awesome-stuff.component';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'awesome-stuff', component: AwesomeStuffComponent },
];

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

I noticed that when I tested that col-md-2 might be a but small for the menu so increasing it to col-md-4 and setting the content to col-md-10 did the trick! This is just a small detail.

And we are now done!

Creating a simple Angular 10 App with Nodejs Back-End using MySQL

When application is finished you can create, read, update and delete posts in Angular 10

The combination with Angular, Nodejs and MySQL is very powerful. I am a huge fan of MySQL, started using it in 2007 and here we are, 2020 and people still using it a lot for websites and web services. On top of that, I just found out that MySQL was created by some Swedes back in 1995, the same country as where I am from! Okay now let’s get started.

What we are going to do is super a simple Angular app which where you can create, read, update and delete (CRUD) articles The articles will contain properties such as id, title and category. Hopefully this will get you inspired to create more similar improved projects, also, note that there is no security thinking here, this is just to show how to get started with Angular 10 and MySQL.

First create a fresh directory for your app named anything you want, I’m going with “myapp” as name. In here we will create an Angular app.

Verify that you your Angular version is not too old, we are using Angular 10 for this project.

ng --version

If you are not too far from version 10 you can probably just simply do the ng update like this, if you still have problems check out the Angular Update Guide here.

ng update @angular/core @angular/cli

If you don’t have Angular installed at all, run the command

npm install -g @angular/cli

Create your Angular project

ng new myapp

I am going to go with yes on routing and CSS as preferred styling syntax. If you don’t have angular installed, please install it first by running the command npm install -g @angular/cli.

So when you have created your angular app with the ng new myapp command, let’s check that it is alive and well, go to your folder with the cd command.

cd .\myapp

Now in your app directory, run your application with the serve angular command.

ng serve
Result after creating a new Angular app

Create a Node.js Server

Great it looks good! Next step is to go back to your main directory with the cd command and create a server folder with the mkdir command. This is going to be the back-end, the nodejs folder.

cd ..
mkdir server

Now we have a server folder and a myapp folder. Jump into the folder and create a server.js file. In this file,

let app = require('express')(),
    server = require('http').Server(app),
    bodyParser = require('body-parser')
express = require('express'),
    cors = require('cors'),
    http = require('http'),
    path = require('path');

let articleRoute = require('./Routes/article'),
    util = require('./Configurations/utils');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.use(cors());

app.use(function (err, req, res, next) {
    return res.send({ "statusCode": util.statusCode.ONE, "statusMessage": util.statusMessage.SOMETHING_WENT_WRONG });
});

app.use('/article', articleRoute);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
    next();
});

server.listen(3000, function () {
    console.log('app listening on port: 3000');
});

What we are doing
We are running a nodejs express server here on port 3000. We also define that on the URL /article we want to use the articleRoute object. In the top of the file we have imported this object in ./Routes/article. We also have our util object at ./Configurations/utils to send statusCodes. We will create these folders and files shortly!

Now let’s install the dependencies from our newly created server.js

npm i express http body-parser cors http path

Hopefully I didn’t miss any dependency, but you will notice if it fails to run later on.

Now create another directory called Routes inside the server folder, and inside the Routes folder create a file called article.js

let express = require('express'),
    router = express.Router(),
    util = require('../Configurations/utils'),
    articleService = require('../service/article');

/**Api to create article */
router.post('/create-article', (req, res) => {
    console.log(req)
    articleService.createArticle(req.body, (data) => {
        res.send(data);
    });
});

// /**Api to update article */
router.put('/update-article', (req, res) => {
    articleService.updateArticle(req.body, (data) => {
        res.send(data);
    });
});

// /**Api to delete the article */
router.delete('/delete-article', (req, res) => {
    articleService.deleteArticle(req.query, (data) => {
        res.send(data);
    });
});

/**Api to get the list of article */
router.get('/get-article', (req, res) => {
    articleService.getArticle(req.query, (data) => {
        res.send(data);
    });
});

// /**API to get the article by id... */
router.get('/get-article-by-id', (req, res) => {
    articleService.getArticleById(req.query, (data) => {
        res.send(data);
    });
});

module.exports = router;

Install the dependencies

npm i async xml2js

With this routing file we now have some API routes for our articles in our database. Don’t worry about the database yet, we will come to that.

Now create another folder inside the server folder called Configurations and inside it, create a file called config.js.

let environment = "dev";

let serverURLs = {
    "dev": {
        "NODE_SERVER": "http://localhost",
        "NODE_SERVER_PORT": "3000",
        "MYSQL_HOST": 'localhost',
        "MYSQL_USER": 'root',
        "MYSQL_PASSWORD": 'password',
        'MYSQL_DATABASE': 'mydb',
    }
}

let config = {
    "DB_URL_MYSQL": {
        "host": `${serverURLs[environment].MYSQL_HOST}`,
        "user": `${serverURLs[environment].MYSQL_USER}`,
        "password": `${serverURLs[environment].MYSQL_PASSWORD}`,
        "database": `${serverURLs[environment].MYSQL_DATABASE}`
    },
    "NODE_SERVER_PORT": {
        "port": `${serverURLs[environment].NODE_SERVER_PORT}`
    },
    "NODE_SERVER_URL": {
        "url": `${serverURLs[environment].NODE_SERVER}`
    }
};

module.exports = {
    config: config
};

Next up, create a mysqlconfig.js file inside the Configurations folder.

var config = require("../Configurations/config").config;
var mysql = require('mysql');
var connection = mysql.createConnection({
    host: config.DB_URL_MYSQL.host,
    user: config.DB_URL_MYSQL.user,
    password: config.DB_URL_MYSQL.password,
    database: config.DB_URL_MYSQL.database,
});

connection.connect((er) => {
    console.log(er)
});

let getDB = () => {
    return connection;
}

module.exports = {
    getDB: getDB
}

Remember to install the mysql dependency

npm i mysql

Next create a utils.js file, still inside the Configurations folder. We will define some status messages.

// Define Error Codes
let statusCode = {
    OK: 200,
    FOUR_ZERO_FOUR: 404,
    FOUR_ZERO_THREE: 403,
    FOUR_ZERO_ONE: 401,
    FIVE_ZERO_ZERO: 500
};

// Define Error Messages
let statusMessage = {
    SERVER_BUSY: 'Our Servers are busy. Please try again later.',
    DATA_UPDATED: 'Data updated successfully.',
    DELETE_DATA: 'Delete data successfully',

};

module.exports = {
    statusCode: statusCode,
    statusMessage: statusMessage
}

Now create a new DAO folder in the server folder to handle the SQL queries and add a file called articleDAO.js inside it.

let dbConfig = require("../Configurations/mysqlconnection");

let getArticle = (criteria, callback) => {
    //criteria.aricle_id ? conditions += ` and aricle_id = '${criteria.aricle_id}'` : true;
    dbConfig.getDB().query(`select * from article where 1`, criteria, callback);
}

let getArticleDetail = (criteria, callback) => {
    let conditions = "";
    criteria.id ? conditions += ` and id = '${criteria.id}'` : true;
    dbConfig.getDB().query(`select * from article where 1 ${conditions}`, callback);
}

let createArticle = (dataToSet, callback) => {
    console.log("insert into article set ? ", dataToSet, 'fileidea')
    dbConfig.getDB().query("insert into article set ? ", dataToSet, callback);
}

let deleteArticle = (criteria, callback) => {
    let conditions = "";
    criteria.id ? conditions += ` and id = '${criteria.id}'` : true;
    console.log(`delete from article where 1 ${conditions}`);
    dbConfig.getDB().query(`delete from article where 1 ${conditions}`, callback);

}

let updateArticle = (criteria, dataToSet, callback) => {
    let conditions = "";
    let setData = "";
    criteria.id ? conditions += ` and id = '${criteria.id}'` : true;
    dataToSet.category ? setData += `category = '${dataToSet.category}'` : true;
    dataToSet.title ? setData += `, title = '${dataToSet.title}'` : true;
    console.log(`UPDATE article SET ${setData} where 1 ${conditions}`);
    dbConfig.getDB().query(`UPDATE article SET ${setData} where 1 ${conditions}`, callback);
}
module.exports = {
    getArticle: getArticle,
    createArticle: createArticle,
    deleteArticle: deleteArticle,
    updateArticle: updateArticle,
    getArticleDetail: getArticleDetail
}

Create a service folder inside the server folder now and add a file called article.js.

let async = require('async'),
    parseString = require('xml2js').parseString;

let util = require('../Configurations/utils'),
    articleDAO = require('../DAO/articleDAO');

/* Creating a new article */
let createArticle = (data, callback) => {
    async.auto({
        article: (cb) => {
            var dataToSet = {
                "category": data.category ? data.category : '',
                "title": data.title,
            }
            console.log(dataToSet);
            articleDAO.createArticle(dataToSet, (err, dbData) => {
                if (err) {
                    cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.SERVER_BUSY });
                    return;
                }

                cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DATA_UPDATED, "result": dataToSet });
            });
        }
        //]
    }, (err, response) => {
        callback(response.article);
    });
}

/* Update an article */
let updateArticle = (data, callback) => {
    async.auto({
        articleUpdate: (cb) => {
            if (!data.id) {
                cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.PARAMS_MISSING })
                return;
            }
            console.log('phase 1');
            var criteria = {
                id: data.id,
            }
            var dataToSet = {
                "category": data.category,
                "title": data.title,
            }
            console.log(criteria, 'test', dataToSet);
            articleDAO.updateArticle(criteria, dataToSet, (err, dbData) => {
                if (err) {
                    cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.SERVER_BUSY });
                    return;
                }
                else {
                    cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DATA_UPDATED, "result": dataToSet });
                }
            });
        }
    }, (err, response) => {
        callback(response.articleUpdate);
    });
}

/* Delete an article */
let deleteArticle = (data, callback) => {
    console.log(data, 'data to set')
    async.auto({
        removeArticle: (cb) => {
            if (!data.id) {
                cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.PARAMS_MISSING })
                return;
            }
            var criteria = {
                id: data.id,
            }
            articleDAO.deleteArticle(criteria, (err, dbData) => {
                if (err) {
                    console.log(err);
                    cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage": util.statusMessage.SERVER_BUSY });
                    return;
                }
                cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DELETE_DATA });
            });
        }
    }, (err, response) => {
        callback(response.removeArticle);
    });
}

/* Get all articles  */
let getArticle = (data, callback) => {
    async.auto({
        article: (cb) => {
            articleDAO.getArticle({}, (err, data) => {
                if (err) {
                    cb(null, { "errorCode": util.statusCode.INTERNAL_SERVER_ERROR, "statusMessage": util.statusMessage.SERVER_BUSY });
                    return;
                }
                cb(null, data);
                return;
            });
        }
    }, (err, response) => {
        callback(response.article);
    })
}

/* Get article by Id */
let getArticleById = (data, callback) => {
    async.auto({
        article: (cb) => {
            let criteria = {
                "id": data.id
            }
            articleDAO.getArticleDetail(criteria, (err, data) => {
                if (err) {
                    console.log(err, 'error----');
                    cb(null, { "errorCode": util.statusCode.INTERNAL_SERVER_ERROR, "statusMessage": util.statusMessage.SERVER_BUSY });
                    return;
                }
                cb(null, data[0]);
                return;
            });
        }
    }, (err, response) => {
        callback(response.article);
    })
}

module.exports = {
    createArticle: createArticle,
    updateArticle: updateArticle,
    deleteArticle: deleteArticle,
    getArticle: getArticle,
    getArticleById: getArticleById
};

Now we have a full service for the full article object in our database utilising the articleDAO.js file which works like an interface for our service.

To start your server run node server.js inside your server folder. It will be hosted on localhost:3000. Also since we haven’t made any settings to the database yet you will get database errors if you are trying to get any articles through your API.

Proceed with Angular app

Our nodejs express server is now pretty much done! In the end we will we will adjust the database settings if you haven’t already done it. Now let’s go back to the angular application folder (myapp) in the terminal. Then generate a component with the angular command.

ng g component article

Bam! We got a component ready to do stuff with. Now to communicate with our back-end service, we can use the HttpClientModule from Angular. Let’s add it to our angular app inside app.module.ts. Open and edit it and add the HttpClientModule to the imports. We also want to add ReactiveFormsModule and FormsModule because we will use formGroup soon in our component. Note that the generate command already added our ArticleComponent to the module. We will also add ArticleService, which we will add in the next step, so expect an error here.

My app.module.ts looks like this now:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ArticleComponent } from './article/article.component';
import { HttpClientModule } from '@angular/common/http';
import { ArticleService } from './article.service';
import { HttpModule } from '@angular/http';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent,
    ArticleComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    HttpModule,
    CommonModule,
    ReactiveFormsModule
  ],
  exports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule
  ],
  providers: [ArticleService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now let’s add an Article class/model that we will use whenever doing things with our articles. Create a folder Models in your app folder, and in it, create a file called article.model.ts. Here we can add as many properties as we want, but first of all, we need to include the ones that will be in the database table.

export class Article {
    id: number
    title: string
    category: string
}

Now we need a service which can talk to the back-end. Run the Angular command to create a service with the following in your angular project folder.

ng g service article

Open up the generated article.service.ts file and add

import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Observable, pipe, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Article } from './model/article.model';


@Injectable()
export class ArticleService {
  // URL for CRUD operations
  articleUrl = "http://localhost:3000/article";
  // Create constructor to get Http instance
  constructor(private http: Http) {
  }

  // Fetch all articles
  getAllArticles(): Observable<Article[]> {
    return this.http.get(this.articleUrl + "/get-article")
      .pipe(map(this.extractData))

  }
  // Create article
  createArticle(article: Article): Observable<number> {
    let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: cpHeaders });
    console.log(article)
    return this.http.post(this.articleUrl + "/create-article", article, options)
      .pipe(
        map(success => success.status)
      )
  }
  // Fetch article by id
  getArticleById(articleId: string): Observable<Article> {
    let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: cpHeaders });
    console.log(this.articleUrl + "/get-article-by-id?id=" + articleId);
    return this.http.get(this.articleUrl + "/get-article-by-id?id=" + articleId)
      .pipe(map(this.extractData))
  }
  // Update article
  updateArticle(article: Article): Observable<number> {
    let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: cpHeaders });
    return this.http.put(this.articleUrl + "/update-article", article, options)
      .pipe(map(success => success.status))
  }
  // Delete article    
  deleteArticleById(articleId: string): Observable<number> {
    let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: cpHeaders });
    return this.http.delete(this.articleUrl + "/delete-article?id=" + articleId)
      .pipe(map(success => success.status))

  }
  private extractData(res: Response) {
    let body = res.json();
    return body;
  }
  private handleError(error: Response | any) {
    console.error(error.message || error);
    return Observable.throw(error.status);
  }
}

Great we got the service down to talk with our back-end.

Let’s start using the service, open up the article.component.ts file and add:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Article } from '../model/article.model';
import { ArticleService } from '../article.service'

@Component({
  selector: 'app-article',
  templateUrl: './article.component.html',
  styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
  //Component properties
  allArticles: Article[];
  statusCode: number;
  requestProcessing = false;
  articleIdToUpdate = null;
  processValidation = false;
  //Create form
  articleForm = new FormGroup({
    title: new FormControl('', Validators.required),
    category: new FormControl('', Validators.required)
  });
  //Create constructor to get service instance
  constructor(private articleService: ArticleService) {
  }
  //Create ngOnInit() and and load articles
  ngOnInit(): void {
    this.getAllArticles();
  }

  //Fetch all articles
  getAllArticles() {
    this.articleService.getAllArticles()
      .subscribe(
        data => this.allArticles = data,
        errorCode => this.statusCode = errorCode);

  }
  //Handle create and update article
  onArticleFormSubmit() {
    this.processValidation = true;
    if (this.articleForm.invalid) {
      return; //Validation failed, exit from method.
    }
    //Form is valid, now perform create or update
    this.preProcessConfigurations();
    let article = this.articleForm.value;
    console.log(article)
    if (this.articleIdToUpdate === null) {
      //Generate article id then create article
      this.articleService.getAllArticles()
        .subscribe(articles => {
          let articleId = 0
          //Generate article id    
          if (articles.length > 0) {
            let maxIndex = articles.length - 1;

            let articleWithMaxIndex = articles[maxIndex];
            articleId = articleWithMaxIndex.id + 1;
          }
          article.id = articleId;
          console.log(article, 'this is form data---');
          //Create article
          this.articleService.createArticle(article)
            .subscribe(successCode => {
              this.statusCode = successCode;
              this.getAllArticles();
              this.backToCreateArticle();
            },
              errorCode => this.statusCode = errorCode
            );
        });
    } else {
      //Handle update article
      article.id = this.articleIdToUpdate;
      this.articleService.updateArticle(article)
        .subscribe(successCode => {
          this.statusCode = successCode;
          this.getAllArticles();
          this.backToCreateArticle();
        },
          errorCode => this.statusCode = errorCode);
    }
  }
  //Load article by id to edit
  loadArticleToEdit(articleId: string) {
    this.preProcessConfigurations();
    this.articleService.getArticleById(articleId)
      .subscribe(article => {
        console.log(article, 'poiuytre');
        this.articleIdToUpdate = article.id;
        this.articleForm.setValue({ title: article.title, category: article.category });
        this.processValidation = true;
        this.requestProcessing = false;
      },
        errorCode => this.statusCode = errorCode);
  }
  //Delete article
  deleteArticle(articleId: string) {
    this.preProcessConfigurations();
    this.articleService.deleteArticleById(articleId)
      .subscribe(successCode => {
        //this.statusCode = successCode;
        //Expecting success code 204 from server
        this.statusCode = 204;
        this.getAllArticles();
        this.backToCreateArticle();
      },
        errorCode => this.statusCode = errorCode);
  }
  //Perform preliminary processing configurations
  preProcessConfigurations() {
    this.statusCode = null;
    this.requestProcessing = true;
  }
  //Go back from update to create
  backToCreateArticle() {
    this.articleIdToUpdate = null;
    this.articleForm.reset();
    this.processValidation = false;
  }
}

To keep it simple we will run a getAllArticles method to get all articles whenever the component is initialised. Now let us create the component html. Open up article.component.html and add:

<h1 class="text-center">FileIdea Angular 10 app</h1>
<h3 class="text-center" *ngIf="articleIdToUpdate; else create">
    Update Article for Id: {{articleIdToUpdate}}
</h3>
<ng-template #create>
    <h3 class="text-center"> Create New Article </h3>
</ng-template>
<div>
    <form [formGroup]="articleForm" (ngSubmit)="onArticleFormSubmit()">
        <table class="table-striped" style="margin:0 auto;">
            <tr>
                <td>Enter Title</td>
                <td><input formControlName="title">
                    <label *ngIf="articleForm.get('title').invalid && processValidation" [ngClass]="'error'"> Title is
                        required. </label>
                </td>
            </tr>
            <tr>
                <td>Enter Category</td>
                <td><input formControlName="category">
                    <label *ngIf="articleForm.get('category').invalid && processValidation" [ngClass]="'error'">
                        Category is required. </label>
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <button class="btn btn-default" *ngIf="!articleIdToUpdate">CREATE</button>
                    <button class="btn btn-default" *ngIf="articleIdToUpdate">UPDATE</button>
                    <button (click)="backToCreateArticle()" *ngIf="articleIdToUpdate">Go Back</button>
                </td>
            </tr>
        </table>
    </form>
    <br />
    <div class="text-center" *ngIf="statusCode; else processing">
        <div *ngIf="statusCode === 201" [ngClass]="'success'">
            Article added successfully.
        </div>
        <div *ngIf="statusCode === 409" [ngClass]="'success'">
            Article already exists.
        </div>
        <div *ngIf="statusCode === 200" [ngClass]="'success'">
            Article updated successfully.
        </div>
        <div *ngIf="statusCode === 204" [ngClass]="'success'">
            Article deleted successfully.
        </div>
        <div *ngIf="statusCode === 500" [ngClass]="'error'">
            Internal Server Error.
        </div>
    </div>
    <ng-template #processing>
        <!-- <img *ngIf="requestProcessing" src="assets/images/loading.gif"> -->
        <p *ngIf="requestProcessing">Loading</p>
    </ng-template>
</div>
<h3 class="text-center">Article List</h3>
<table class="table-striped" style="margin:0 auto;" *ngIf="allArticles">
    <tr>
        <th> Id</th>
        <th>Title</th>
        <th>Category</th>
        <th></th>
        <th></th>
    </tr>
    <tr *ngFor="let article of allArticles">
        <td>{{article.id}}</td>
        <td>{{article.title}}</td>
        <td>{{article.category}}</td>
        <td><button class="btn btn-default" type="button" (click)="loadArticleToEdit(article.id)">Edit</button> </td>
        <td><button class="btn btn-default" type="button" (click)="deleteArticle(article.id)">Delete</button></td>
    </tr>
</table>

Okay we got a back end server, a service in our Angular app, and markup for our component. What is next? Well let us add the new component to our app.component.html. You can remove everything there and replace it with   <app-article></app-article> if you want. I am actually going to keep the sample CSS because I like what they have going on with the blue top bar, and then I’ll put my component element inside the content class they already made, and keep router-outlet in the bottom. In the end, my app.component.html looks like this.

<style>
  :host {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    font-size: 14px;
    color: #333;
    box-sizing: border-box;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    margin: 8px 0;
  }

  p {
    margin: 0;
  }

  .spacer {
    flex: 1;
  }

  .toolbar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 60px;
    display: flex;
    align-items: center;
    background-color: #1976d2;
    color: white;
    font-weight: 600;
  }

  .toolbar img {
    margin: 0 16px;
  }

  .toolbar #twitter-logo {
    height: 40px;
    margin: 0 16px;
  }

  .toolbar #twitter-logo:hover {
    opacity: 0.8;
  }

  .content {
    display: flex;
    margin: 82px auto 32px;
    padding: 0 16px;
    max-width: 960px;
    flex-direction: column;
    align-items: center;
  }

  svg.material-icons {
    height: 24px;
    width: auto;
  }

  svg.material-icons:not(:last-child) {
    margin-right: 8px;
  }


  svg#clouds {
    position: fixed;
    bottom: -160px;
    left: -230px;
    z-index: -10;
    width: 1920px;
  }


  /* Responsive Styles */
  @media screen and (max-width: 767px) {

    .card-container>*:not(.circle-link),
    .terminal {
      width: 100%;
    }

    .card:not(.highlight-card) {
      height: 16px;
      margin: 8px 0;
    }

    .card.highlight-card span {
      margin-left: 72px;
    }

    svg#rocket-smoke {
      right: 120px;
      transform: rotate(-5deg);
    }
  }

  @media screen and (max-width: 575px) {
    svg#rocket-smoke {
      display: none;
      visibility: hidden;
    }
  }
</style>


<!-- Toolbar -->
<div class="toolbar" role="banner">
  <img width="40" alt="Angular Logo"
    src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==" />
  <span>Welcome</span>
  <div class="spacer"></div>
  <a aria-label="Angular on twitter" target="_blank" rel="noopener" href="https://twitter.com/angular" title="Twitter">
    <svg id="twitter-logo" height="24" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
      <rect width="400" height="400" fill="none" />
      <path
        d="M153.62,301.59c94.34,0,145.94-78.16,145.94-145.94,0-2.22,0-4.43-.15-6.63A104.36,104.36,0,0,0,325,122.47a102.38,102.38,0,0,1-29.46,8.07,51.47,51.47,0,0,0,22.55-28.37,102.79,102.79,0,0,1-32.57,12.45,51.34,51.34,0,0,0-87.41,46.78A145.62,145.62,0,0,1,92.4,107.81a51.33,51.33,0,0,0,15.88,68.47A50.91,50.91,0,0,1,85,169.86c0,.21,0,.43,0,.65a51.31,51.31,0,0,0,41.15,50.28,51.21,51.21,0,0,1-23.16.88,51.35,51.35,0,0,0,47.92,35.62,102.92,102.92,0,0,1-63.7,22A104.41,104.41,0,0,1,75,278.55a145.21,145.21,0,0,0,78.62,23"
        fill="#fff" />
    </svg>
  </a>
</div>

<div class="content" role="main">
  <!-- Article Component -->
  <app-article></app-article>

</div>


<router-outlet></router-outlet>

MySQL

To get the MySQL working, open up our favourite tool. I am using XAMPP which has phpmyadmin for MySQL which never seems to have any problems. Download it here. Start your Apache (you might need to change port) and MySQL and click on the admin button to view the phpmyadmin page.

Once you are in phpmyadmin, create a database and in it, create a article table. My phpmyadmin looks like this now.

CREATE TABLE `article` (
  `id` int(11) NOT NULL,
  `title` varchar(80) NOT NULL,
  `category` varchar(80) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


ALTER TABLE `article`
  ADD PRIMARY KEY (`id`);


ALTER TABLE `article`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

If you are feeling copy-pasty you can run the sql above to your test database or whatever you called it. Now we need to update our sql database settings in our server, inside the config.js inside the Configurations folder. Now I have a test database server and running this on localhost, I do not have a password. This is how my config.js looks like

let environment = "dev";

let serverURLs = {
    "dev": {
        "NODE_SERVER": "http://localhost",
        "NODE_SERVER_PORT": "3306",
        "MYSQL_HOST": '127.0.0.1',
        "MYSQL_USER": 'root',
        "MYSQL_PASSWORD": '',
        'MYSQL_DATABASE': 'test',
    }
}

let config = {
    "DB_URL_MYSQL": {
        "host": `${serverURLs[environment].MYSQL_HOST}`,
        "user": `${serverURLs[environment].MYSQL_USER}`,
        "password": `${serverURLs[environment].MYSQL_PASSWORD}`,
        "database": `${serverURLs[environment].MYSQL_DATABASE}`
    },
    "NODE_SERVER_PORT": {
        "port": `${serverURLs[environment].NODE_SERVER_PORT}`
    },
    "NODE_SERVER_URL": {
        "url": `${serverURLs[environment].NODE_SERVER}`
    }
};

module.exports = {
    config: config
};

Mission Complete

You should now have a app that uses MySQL with Angular 10! Open up your Angular app on localhost:4200 and behold the beauty and start inserting new rows into your article table.