Download as pdf or txt
Download as pdf or txt
You are on page 1of 48

11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

Home (/) > Programming Blog (/post-category/595f867edbd39e7571e183dc/programming-blog) > Node.js (/post-sub-


category/58a9196f80aca748640ce352/nodejs)

MEAN Stack (Angular 8) Tutorial: Build a Simple Blog


CMS
by Didin J. on Sep 23, 2019

A comprehensive step by step MEAN Stack (Angular 8) tutorial on build a simple Blog CMS (Content
Management System)

In this MEAN Stack (Angular 8) tutorial, we will build a simple blog CMS that can add, edit, delete and view blog post and
category. So, there are two entities or models that required for this simple blog CMS. There only authenticated users that can
access this CMS. After user login, user can do CRUD (create, read, update, delete) operation on post and category models.

Table of Contents:
New Node Express.js App using Express Generator
Install Mongoose.js
Install Passport.js
Add Mongoose Models or Schemas
Add Express Router for Login and Register
Add Secure Express Router for Category CRUD
Add Secure Express Router for Post CRUD
Add Non-Secure Express Router for Front Page
New Angular 8 Web App using Angular CLI
Add Angular 8 Routing and Navigation
Add a custom Angular 8 HttpInterceptor
Add Angular 8 Service (HttpClient, RxJS, Observable)
Add Angular 8 Material and CDK
Add Angular Material Login and Register Components
Add Angular Material Blog Category CRUD Component
Add Angular Material Blog Post CRUD Component
Secure the Components using Angular 8 Route Guard
Add Angular Material Blog Front Page
Run and Test the MEAN Stack (Angular 8) Blog CMS
The following tools, frameworks, and modules are required for this tutorial achievement:

1. Node.js (https://nodejs.org/)
2. MongoDB (https://www.mongodb.com/)
3. Angular 8 (https://angular.io/)
4. Angular CLI (https://cli.angular.io/)
5. Express.js (https://expressjs.com/)
6. Passport.js (https://passportjs.org/)
7. Mongoose.js (https://mongoosejs.com/)
8. CKEditor 4 (https://ckeditor.com/ckeditor-4/)
9. Terminal or Command Line
10. IDE or Text Editor

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 1/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
Before the move to the main steps of this tutorial, make sure that you have installed Node.js and MongoDB on your machine.
You can check the Node.js version after installing it from the terminal or Node.js command line.

node -v
v10.15.1
npm -v
6.11.3

New Node Express.js App using Express Generator


As you see in the first paragraph of this tutorial the terms "E" is Express.js. Express is a minimal and flexible Node.js web
application framework that provides a robust set of features for web and mobile applications. To create Express.js app, we will
be using Express generator. Type this command to install it.

sudo npm install -g express-generator

Next, create an Express.js app by typing this command.

express blog-cms --no-view

Go to the newly created blog-cms folder then install all NPM modules.

cd ./blog-cms
npm install

Now, we have this Express.js app structure for the blog-cms app.

To check and sanitize the Express.js app, run this app for the first time.

nodemon

or

npm start

Then you will see this page when open the browser and go to `localhost:3000`.

Install Mongoose.js
We will use Mongoose as the ODM for MongoDB. Mongoose provides a straight-forward, schema-based solution to model your
application data. It includes built-in type casting, validation, query building, business logic hooks and more, out of the box. To
install Mongoose.js and it's required dependencies, type this command.

npm install --save mongoose bluebird

Next, open and edit `app.js` then declare the Mongoose module.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 2/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

var mongoose = require('mongoose');

Create a connection to the MongoDB server using these lines of codes.

mongoose.connect('mongodb://localhost/blog-cms', {
promiseLibrary: require('bluebird'),
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
}).then(() => console.log('connection successful'))
.catch((err) => console.error(err));

Now, if you re-run again Express.js server after running MongoDB server or daemon, you will see this information in the console.

[nodemon] 1.18.6
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node ./bin/www`
connection successful

That's mean, the connection to the MongoDB is successful.

Install Passport.js
We will use Passport.js for authentication or user login. Passport is authentication middleware for Node.js. Extremely flexible
and modular, Passport can be unobtrusively dropped into any Express-based web application. A comprehensive set of
strategies support authentication using a username and password, Facebook, Twitter, and more. To install Passport.js and it's
required dependencies, type this command.

npm install --save bcrypt-nodejs jsonwebtoken morgan passport passport-jwt

Create a new folder to holds configuration files then add configuration files to `config` folder.

mkdir config
touch config/settings.js
touch config/passport.js

Open and edit `config/settings.js` then add these lines of codes.

module.exports = {
'secret':'mevnsecure'
};

That file holds a secret code for generating a JWT token. Next, open and edit `config/passport.js` then add these lines of codes.

var JwtStrategy = require('passport-jwt').Strategy,


ExtractJwt = require('passport-jwt').ExtractJwt;

// load up the user model


var User = require('../models/user');
var settings = require('../config/settings'); // get settings file

module.exports = function(passport) {
var opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme("jwt");
opts.secretOrKey = settings.secret;
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({id: jwt_payload.id}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
};

This config is used for getting the user by matching JWT token with token get from the client. This configuration needs to
create a User model. Now, Open and edit `app.js` then declare required library for initializing with the server by adding these
lines of requires.

var passport = require('passport');

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 3/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
Declare a variable for Authentication, Category, and Post route.

var auth = require('./routes/auth');


var category = require('./routes/category');
var post = require('./routes/post');

Initialize passport by adding this line after the declaration of app variable.

app.use(passport.initialize());

Add API route to the endpoint URL after other `use` function.

app.use('/api/auth', auth);
app.use('/api/category', category);
app.use('/api/post', post);

Add Mongoose Models or Schemas


We will use MongoDB collections for User, Category, and Post. For that, we need to create new Mongoose models or schemas
for them. First, create a new folder in the root of the project folder that holds the Mongoose models or schemas files then add
those required models files.

mkdir models
touch models/User.js
touch models/Category.js
touch models/Post.js

Open and edit `models/User.js` then add these codes of the required Username and Password fields. Also, password encryption
using Bcrypt and compare password that saved in the MongoDB collection and encrypted plain password from the request
body.

var mongoose = require('mongoose');


var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');

var UserSchema = new Schema({


username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
}
});

UserSchema.pre('save', function (next) {


var user = this;
if (this.isModified('password') || this.isNew) {
bcrypt.genSalt(10, function (err, salt) {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, null, function (err, hash) {
if (err) {
return next(err);
}
user.password = hash;
next();
});
});
} else {
return next();
}
});

UserSchema.methods.comparePassword = function (passw, cb) {


bcrypt.compare(passw, this.password, function (err, isMatch) {
if (err) {
return cb(err);
}
cb(null, isMatch);
});
};

module.exports = mongoose.model('User', UserSchema);

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 4/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
Next, open and edit `models/Category.js` then add these Javascript codes of the required fields for the category.

var mongoose = require('mongoose');

var CategorySchema = new mongoose.Schema({


id: String,
catName: String,
catDesc: String,
catImgUrl: String,
catContent: String,
updated: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Category', CategorySchema);

Next, open and edit `models/Post.js` then add these Javascript codes of the required fields for the article post including a
reference from the Category collection.

var mongoose = require('mongoose'), Schema = mongoose.Schema;

var PostSchema = new mongoose.Schema({


category : { type: Schema.Types.ObjectId, ref: 'Category' },
id: String,
postTitle: String,
postAuthor: String,
postDesc: String,
postContent: String,
postReference: String,
postImgUrl: String,
created: { type: Date },
updated: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Post', PostSchema);

Add Express Router for Login and Register


We will create a Router for the user login/register, restricted category, and post resources. In the Express routes folder creates a
new Javascript file by type this command.

touch routes/auth.js

Open and edit `routes/auth.js` then declares all require variables of Mongoose models, Passport.js, JWT, Express Router, and
Configuration file.

var mongoose = require('mongoose');


var passport = require('passport');
var config = require('../config/database');
require('../config/passport')(passport);
var express = require('express');
var jwt = require('jsonwebtoken');
var router = express.Router();
var User = require("../models/user");

Create a router to register the new user using just a username and password.

router.post('/login', function(req, res) {


if (!req.body.username || !req.body.password) {
res.json({success: false, msg: 'Please pass username and password.'});
} else {
var newUser = new User({
username: req.body.username,
password: req.body.password
});
// save the user
newUser.save(function(err) {
if (err) {
return res.json({success: false, msg: 'Username already exists.'});
}
res.json({success: true, msg: 'Successful created new user.'});
});
}
});

Create a router for login or sign-in using username and password.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 5/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

router.post('/register', function(req, res) {


User.findOne({
username: req.body.username
}, function(err, user) {
if (err) throw err;

if (!user) {
res.status(401).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
// check if password matches
user.comparePassword(req.body.password, function (err, isMatch) {
if (isMatch && !err) {
// if user is found and password is right create a token
var token = jwt.sign(user.toJSON(), config.secret);
// return the information including token as JSON
res.json({success: true, token: 'JWT ' + token});
} else {
res.status(401).send({success: false, msg: 'Authentication failed. Wrong password.'});
}
});
}
});
});

Create a router for logout.

router.post('/logout', passport.authenticate('jwt', { session: false}), function(req, res) {


req.logout();
res.json({success: true});
});

Next, export router as a module.

module.exports = router;

Add Secure Express Router for Category CRUD


Next, we will add a router that contains CRUD operation for the Category. First, create a new file inside the routes folder.

touch routes/category.js

Open and edit `routes/category.js` then add these required modules of the Mongoose model, Passport.js, JWT, Express Router,
and Configuration file.

var passport = require('passport');


var config = require('../config/database');
require('../config/passport')(passport);
var express = require('express');
var jwt = require('jsonwebtoken');
var router = express.Router();
var Category = require("../models/category");

Add a route to get the list of the category.

router.get('/', passport.authenticate('jwt', { session: false}), function(req, res) {


var token = getToken(req.headers);
if (token) {
Category.find(function (err, categories) {
if (err) return next(err);
res.json(categories);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a route to get a single category by ID.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 6/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

router.get('/:id', passport.authenticate('jwt', { session: false}), function(req, res, next) {


var token = getToken(req.headers);
if (token) {
Category.findById(req.params.id, function (err, category) {
if (err) return next(err);
res.json(category);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a route to post a category.

router.post('/', passport.authenticate('jwt', { session: false}), function(req, res, next) {


var token = getToken(req.headers);
if (token) {
Category.create(req.body, function (err, category) {
if (err) return next(err);
res.json(category);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a route to put a category by ID.

router.put('/:id', passport.authenticate('jwt', { session: false}), function(req, res, next) {


var token = getToken(req.headers);
if (token) {
Category.findByIdAndUpdate(req.params.id, req.body, function (err, category) {
if (err) return next(err);
res.json(category);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a route to delete a category by ID.

router.delete('/:id', passport.authenticate('jwt', { session: false}), function(req, res, next) {


var token = getToken(req.headers);
if (token) {
Category.findByIdAndRemove(req.params.id, req.body, function (err, category) {
if (err) return next(err);
res.json(category);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a function to get and extract the token from the request headers.

getToken = function (headers) {


if (headers && headers.authorization) {
var parted = headers.authorization.split(' ');
if (parted.length === 2) {
return parted[1];
} else {
return null;
}
} else {
return null;
}
};

Next, export router as a module.

module.exports = router;

Add Secure Express Router for Post CRUD


Next, we will add a router that contains CRUD operation for the Category. First, create a new file inside the routes folder.

touch routes/post.js

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 7/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
Open and edit `routes/post.js` then add these required modules of the Mongoose model, Passport.js, JWT, Express Router, and
Configuration file.

var passport = require('passport');


var config = require('../config/database');
require('../config/passport')(passport);
var express = require('express');
var jwt = require('jsonwebtoken');
var router = express.Router();
var Post = require("../models/category");

Add a route to GET the list of posts.

router.get('/', passport.authenticate('jwt', { session: false}), function(req, res) {


var token = getToken(req.headers);
if (token) {
Post.find(function (err, posts) {
if (err) return next(err);
res.json(posts);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a route to GET a single post data by ID.

router.get('/:id', passport.authenticate('jwt', { session: false}), function(req, res, next) {


var token = getToken(req.headers);
if (token) {
Post.findById(req.params.id, function (err, post) {
if (err) return next(err);
res.json(post);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a route to POST a post data.

router.post('/', passport.authenticate('jwt', { session: false}), function(req, res, next) {


var token = getToken(req.headers);
if (token) {
Post.create(req.body, function (err, post) {
if (err) return next(err);
res.json(post);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a router to PUT a post data by ID.

router.put('/:id', passport.authenticate('jwt', { session: false}), function(req, res, next) {


var token = getToken(req.headers);
if (token) {
Post.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
if (err) return next(err);
res.json(post);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

Add a router to DELETE a post data by ID.

router.delete('/:id', passport.authenticate('jwt', { session: false}), function(req, res, next) {


var token = getToken(req.headers);
if (token) {
Post.findByIdAndRemove(req.params.id, req.body, function (err, post) {
if (err) return next(err);
res.json(post);
});
} else {
return res.status(403).send({success: false, msg: 'Unauthorized.'});
}
});

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 8/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
Add a function to get and extract the token from the request headers.

getToken = function (headers) {


if (headers && headers.authorization) {
var parted = headers.authorization.split(' ');
if (parted.length === 2) {
return parted[1];
} else {
return null;
}
} else {
return null;
}
};

Next, export the router a module.

module.exports = router;

Add Non-Secure Express Router for Front Page


We will use an existing Express.js `routes/index.js` to route the required data from the category and post. For that, open and
edit `routes/index.js` then add the required Mongoose schema modules.

var Category = require("../models/category");


var Post = require("../models/post");

Next, add these lines of routers to GET category list, post list, and single post by ID.

router.get('/category', function(req, res, next) {


Category.find(function (err, categories) {
if (err) return next(err);
res.json(categories);
});
});

router.get('/bycategory/:id', function(req, res, next) {


Post.find({category: req.params.id}, function (err, posts) {
if (err) return next(err);
res.json(posts);
});
});

router.get('/post', function(req, res, next) {


Post.find(function (err, posts) {
if (err) return next(err);
res.json(posts);
});
});

router.get('/post/:id', function(req, res, next) {


Post.findById(req.params.id, function (err, post) {
if (err) return next(err);
res.json(post);
});
});

module.exports = router;

New Angular 8 Web App using Angular CLI


For the client-side, we will use Angular 8. First, we have to install the Angular 8 CLI. The Angular CLI is a tool to initialize,
develop, scaffold and maintain Angular 8 applications. Go to your Node project folder then type this command for installing the
Angular-CLI.

sudo npm install -g @angular/cli

Now, we have the latest version of Angular when this example was written.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 9/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/

Angular CLI: 8.3.5


Node: 10.15.1
OS: darwin x64
Angular:
...

Package Version
------------------------------------------------------
@angular-devkit/architect 0.803.5
@angular-devkit/core 8.3.5
@angular-devkit/schematics 8.3.5
@schematics/angular 8.3.5
@schematics/update 0.803.5
rxjs 6.4.0

Next, create an Angular 8 web app for this Blog CMS App by typing this command.

ng new client

Answer all questions like below which we will add Angular Routing and use SCSS as a stylesheet.

? Would you like to add Angular routing? Yes


? Which stylesheet format would you like to use? SCSS [ https://sass-lang.com/documentation/syntax#scss (https://sass-lang.com/documentat
ion/syntax#scss) ]

Next, to sanitize the newly created Angular 8 project go to that project folder in the new terminal tab then run the Angular
application.

cd ./client
ng serve

You will this page or view when you open "http://localhost:4200/ (http://localhost:4200/)" in your browser which means the
Angular 8 is ready to go.

ng serve

Open the browser then go to this URL `localhost:4200` and you will see this Angular 8 landing page.

Add Angular 8 Routing and Navigation


On the previous steps, we have to add Angular 8 Routes when answering the questions. Now, we just added the required pages
for Category CRUD, Post CRUD, Login, Register, Home Page, and Post Details. Type this commands to add the Angular 8
components or pages.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 10/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

ng g component auth/login
ng g component auth/register
ng g component home
ng g component admin
ng g component bycategory
ng g component details
ng g component category
ng g component category/category-details
ng g component category/category-add
ng g component category/category-edit
ng g component post
ng g component post/post-details
ng g component post/post-add
ng g component post/post-edit

Open `src/app/app.module.ts` then you will see those components imported and declared in `@NgModule` declarations. Next,
open and edit `src/app/app-routing.module.ts` then add these imports of login, register, home, details, category CRUD, post
CRUD components.

import { LoginComponent } from './auth/login/login.component';


import { RegisterComponent } from './auth/register/register.component';
import { HomeComponent } from './home/home.component';
import { DetailsComponent } from './details/details.component';
import { AdminComponent } from './admin/admin.component';
import { CategoryComponent } from './category/category.component';
import { PostComponent } from './post/post.component';
import { CategoryDetailsComponent } from './category/category-details/category-details.component';
import { CategoryAddComponent } from './category/category-add/category-add.component';
import { CategoryEditComponent } from './category/category-edit/category-edit.component';
import { PostDetailsComponent } from './post/post-details/post-details.component';
import { PostAddComponent } from './post/post-add/post-add.component';
import { PostEditComponent } from './post/post-edit/post-edit.component';
import { BycategoryComponent } from './bycategory/bycategory.component';

Add these arrays of those components routes to the existing routes constant.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 11/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

const routes: Routes = [


{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: 'home',
component: HomeComponent,
data: { title: 'Blog Home' }
},
{
path: 'admin',
component: AdminComponent,
data: { title: 'Blog Admin' }
},
{
path: 'bycategory/:id',
component: BycategoryComponent,
data: { title: 'Post by Category' }
},
{
path: 'details/:id',
component: DetailsComponent,
data: { title: 'Show Post Details' }
},
{
path: 'login',
component: LoginComponent,
data: { title: 'Login' }
},
{
path: 'register',
component: RegisterComponent,
data: { title: 'Register' }
},
{
path: 'category',
component: CategoryComponent,
data: { title: 'Category' }
},
{
path: 'category/details/:id',
component: CategoryDetailsComponent,
data: { title: 'Category Details' }
},
{
path: 'category/add',
component: CategoryAddComponent,
data: { title: 'Category Add' }
},
{
path: 'category/edit/:id',
component: CategoryEditComponent,
data: { title: 'Category Edit' }
},
{
path: 'post',
component: PostComponent,
data: { title: 'Post' }
},
{
path: 'post/details/:id',
component: PostDetailsComponent,
data: { title: 'Post Details' }
},
{
path: 'post/add',
component: PostAddComponent,
data: { title: 'Post Add' }
},
{
path: 'post/edit/:id',
component: PostEditComponent,
data: { title: 'Post Edit' }
}
];

Open and edit `src/app/app.component.html` and you will see the existing router outlet. Next, modify this HTML page to fit the
CRUD page wrapped by <router-outlet>.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 12/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<div class="container">
<router-outlet></router-outlet>
</div>

Open and edit `src/app/app.component.scss` then replace all SCSS codes with this.

.container {
padding: 20px;
}

Add a custom Angular 8 HttpInterceptor


To intercept the JWT token that generated from the successful login to every secure HTTP request, we will use Angular 8
HttpInterceptor. Before creating a custom Angular 8 HttpInterceptor, create a folder with the name
`client/src/app/interceptors`. Next, create a file for the custom Angular 8 HttpInterceptor with the name
`client/src/app/interceptors/token.interceptor.ts`. Open and edit that file the add these imports of the required HTTP handler
and RxJS.

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


import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpResponse,
HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';

Create a class that implementing HttpInterceptor method.

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

Inject the required module to the constructor inside the class.

constructor(private router: Router) {}

Implement a custom Interceptor function.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 13/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

const token = localStorage.getItem('token');


if (token) {
request = request.clone({
setHeaders: {
'Authorization': token
}
});
}
if (!request.headers.has('Content-Type')) {
request = request.clone({
setHeaders: {
'content-type': 'application/json'
}
});
}
request = request.clone({
headers: request.headers.set('Accept', 'application/json')
});
return next.handle(request).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
console.log('event--->>>', event);
}
return event;
}),
catchError((error: HttpErrorResponse) => {
console.log(error);
if (error.status === 401) {
this.router.navigate(['login']);
}
if (error.status === 400) {
alert(error.error);
}
return throwError(error);
}));
}

Next, we have to register this custom HttpInterceptor and HttpClientModule. Open and edit `client/src/app.module.ts` then add
these imports of HTTP_INTERCEPTORS, HttpClientModule, and TokenInterceptor.

import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';


import { TokenInterceptor } from './interceptors/token.interceptor';

Add `HttpClientModule` to the `@NgModule` imports array.

imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],

Add the `Interceptor` modules to the provider array of the `@NgModule`.

providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
}
],

Now, the HTTP interceptor is ready to intercept any request to the API.

Add Angular 8 Service (HttpClient, RxJS, Observable)


To access or consume the Node Express REST API, we need to create a service for that. The Angular 8 service will contain all
login, register, secure, and non-secure CRUD REST API call operation. Type these commands to generate the Angular 8 services
from the client folder.

ng g service auth
ng g service home
ng g service category
ng g service post

Next, open and edit `client/src/app/auth.service.ts` then add these imports of HttpClient, RxJS Observable, of, catchError, and
tap.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 14/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

import { HttpClient } from '@angular/common/http';


import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

Declare a constant variable as Spring Boot REST API URL after the imports.

const apiUrl = 'http://localhost:3000/api/auth/ (http://localhost:3000/api/auth/)';

Declare the variables before the constructor that will use by Angular 8 Route Guard.

@Output() isLoggedIn: EventEmitter<any> = new EventEmitter();


loggedInStatus = false;
redirectUrl: string;

Inject the `HttpClient` module inside the constructor.

constructor(private http: HttpClient) { }

Create all required functions for Login, Logout, Register, and helper functions.

login(data: any): Observable<any> {


return this.http.post<any>(apiUrl + 'login', data)
.pipe(
tap(_ => {
this.isLoggedIn.emit(true);
this.loggedInStatus = true;
}),
catchError(this.handleError('login', []))
);
}

logout(): Observable<any> {
return this.http.post<any>(apiUrl + 'logout', {})
.pipe(
tap(_ => {
this.isLoggedIn.emit(false);
this.loggedInStatus = false;
}),
catchError(this.handleError('logout', []))
);
}

register(data: any): Observable<any> {


return this.http.post<any>(apiUrl + 'register', data)
.pipe(
tap(_ => this.log('login')),
catchError(this.handleError('login', []))
);
}

private handleError<T>(operation = 'operation', result?: T) {


return (error: any): Observable<T> => {

console.error(error); // log to console instead


this.log(`${operation} failed: ${error.message}`);

return of(result as T);


};
}

private log(message: string) {


console.log(message);
}

Next, create an object class that represents category data `client/src/app/category/category.ts` then replace all file contents
with these.

export class Category {


id: number;
catName: string;
catDesc: string;
catImgUrl: string;
catContent: string;
updated: Date;
}

Next, create an object class that represents category data `client/src/app/post/post.ts` then replace all file contents with these.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 15/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

export class Post {


category: string;
id: string;
postTitle: string;
postAuthor: string;
postDesc: string;
postContent: string;
postReference: string;
postImgUrl: string;
created: Date;
updated: Date;
}

Next, open and edit `client/src/app/services/home.service.ts` then replace all codes with this REST API call for the home and
details pages.

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


import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { Category } from './category/category';
import { Post } from './post/post';

const apiUrl = 'http://localhost:3000/api/public/ (http://localhost:3000/api/public/)';

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

constructor(private http: HttpClient) { }

getCategories(): Observable<Category[]> {
return this.http.get<Category[]>(apiUrl + 'category')
.pipe(
tap(_ => this.log('fetched Categories')),
catchError(this.handleError('getCategories', []))
);
}

getPosts(): Observable<Post[]> {
return this.http.get<Post[]>(apiUrl + 'post')
.pipe(
tap(_ => this.log('fetched Posts')),
catchError(this.handleError('getPosts', []))
);
}

getPostsByCategory(id: any): Observable<Post[]> {


return this.http.get<Post[]>(apiUrl + 'bycategory/' + id)
.pipe(
tap(_ => this.log('fetched Posts')),
catchError(this.handleError('getPosts', []))
);
}

getPost(id: any): Observable<Post> {


return this.http.get<Post>(apiUrl + 'post/' + id).pipe(
tap(_ => console.log(`fetched post by id=${id}`)),
catchError(this.handleError<Post>(`getPost id=${id}`))
);
}

private handleError<T>(operation = 'operation', result?: T) {


return (error: any): Observable<T> => {

console.error(error); // log to console instead


this.log(`${operation} failed: ${error.message}`);

return of(result as T);


};
}

private log(message: string) {


console.log(message);
}
}

Next, open and edit `client/src/app/services/category.service.ts` then replace all codes with this REST API call for the category
CRUD operation.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 16/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { Category } from './category/category';

const apiUrl = 'http://localhost:3000/api/category/ (http://localhost:3000/api/category/)';

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

constructor(private http: HttpClient) { }

getCategories(): Observable<Category[]> {
return this.http.get<Category[]>(apiUrl)
.pipe(
tap(_ => this.log('fetched Categories')),
catchError(this.handleError('getCategories', []))
);
}

getCategory(id: any): Observable<Category> {


const url = `${apiUrl}/${id}`;
return this.http.get<Category>(url).pipe(
tap(_ => console.log(`fetched category by id=${id}`)),
catchError(this.handleError<Category>(`getCategory id=${id}`))
);
}

addCategory(category: Category): Observable<Category> {


return this.http.post<Category>(apiUrl, category).pipe(
tap((prod: Category) => console.log(`added category w/ id=${category.id}`)),
catchError(this.handleError<Category>('addCategory'))
);
}

updateCategory(id: any, category: Category): Observable<any> {


const url = `${apiUrl}/${id}`;
return this.http.put(url, category).pipe(
tap(_ => console.log(`updated category id=${id}`)),
catchError(this.handleError<any>('updateCategory'))
);
}

deleteCategory(id: any): Observable<Category> {


const url = `${apiUrl}/${id}`;
return this.http.delete<Category>(url).pipe(
tap(_ => console.log(`deleted category id=${id}`)),
catchError(this.handleError<Category>('deleteCategory'))
);
}

private handleError<T>(operation = 'operation', result?: T) {


return (error: any): Observable<T> => {

console.error(error); // log to console instead


this.log(`${operation} failed: ${error.message}`);

return of(result as T);


};
}

private log(message: string) {


console.log(message);
}
}

Next, open and edit `client/src/app/services/post.service.ts` then replace all codes with this REST API call for the post CRUD
operation.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 17/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { Post } from './post/post';

const apiUrl = 'http://localhost:3000/api/post/ (http://localhost:3000/api/post/)';

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

constructor(private http: HttpClient) { }

getPosts(): Observable<Post[]> {
return this.http.get<Post[]>(apiUrl)
.pipe(
tap(_ => this.log('fetched Posts')),
catchError(this.handleError('getPosts', []))
);
}

getPost(id: any): Observable<Post> {


const url = `${apiUrl}/${id}`;
return this.http.get<Post>(url).pipe(
tap(_ => console.log(`fetched post by id=${id}`)),
catchError(this.handleError<Post>(`getPost id=${id}`))
);
}

addPost(post: Post): Observable<Post> {


return this.http.post<Post>(apiUrl, post).pipe(
tap((prod: Post) => console.log(`added post w/ id=${post.id}`)),
catchError(this.handleError<Post>('addPost'))
);
}

updatePost(id: any, post: Post): Observable<any> {


const url = `${apiUrl}/${id}`;
return this.http.put(url, post).pipe(
tap(_ => console.log(`updated post id=${id}`)),
catchError(this.handleError<any>('updatePost'))
);
}

deletePost(id: any): Observable<Post> {


const url = `${apiUrl}/${id}`;
return this.http.delete<Post>(url).pipe(
tap(_ => console.log(`deleted post id=${id}`)),
catchError(this.handleError<Post>('deletePost'))
);
}

private handleError<T>(operation = 'operation', result?: T) {


return (error: any): Observable<T> => {

console.error(error); // log to console instead


this.log(`${operation} failed: ${error.message}`);

return of(result as T);


};
}

private log(message: string) {


console.log(message);
}
}

Add Angular 8 Material and CDK


Next, for the user interface (UI) we will use Angular 8 Material and CDK. There's a CLI for generating a Material component like
Table as a component, but we will create or add the Table component from scratch to existing component. Type this command
to install Angular 8 Material (@angular/material).

ng add @angular/material

If there are questions like below, just use the default answer.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 18/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink [


Preview: https://material.angular.io?theme=indigo-pink (https://material.angular.io?theme=indigo-pink) ]
? Set up HammerJS for gesture recognition? Yes
? Set up browser animations for Angular Material? Yes

We will register all required Angular 8 Material components or modules to `src/app/app.module.ts`. Open and edit that file then
add these imports of required Angular Material Components.

import {
MatInputModule,
MatPaginatorModule,
MatProgressSpinnerModule,
MatSortModule,
MatTableModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule,
MatMenuModule,
MatToolbarModule } from '@angular/material';

Also, modify `FormsModule` import to add `ReactiveFormsModule`.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to `@NgModule` imports.

imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
MatInputModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatProgressSpinnerModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule,
MatMenuModule,
MatToolbarModule
],

Add Angular Material Login and Register Components


This time for authentication part. Open and edit `client/src/app/auth/login/login.component.ts` then add these imports.

import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';


import { AuthService } from '../../auth.service';
import { Router } from '@angular/router';
import { ErrorStateMatcher } from '@angular/material/core';

Declare these variables before the constructor.

loginForm: FormGroup;
username = '';
password = '';
matcher = new MyErrorStateMatcher();
isLoadingResults = false;

Inject the imported modules to the constructor.

constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }

Initialize `NgForm` to the `NgOnInit` function.

ngOnInit() {
this.loginForm = this.formBuilder.group({
'username' : [null, Validators.required],
'password' : [null, Validators.required]
});
}

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 19/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
Add a function to submit the login form.

onFormSubmit(form: NgForm) {
this.authService.login(form)
.subscribe(res => {
console.log(res);
if (res.token) {
localStorage.setItem('token', res.token);
this.router.navigate(['admin']);
}
}, (err) => {
console.log(err);
});
}

Add a function to go to the Register page.

register() {
this.router.navigate(['register']);
}

Add a class that handles the form validation above this class.

/** Error when invalid control is dirty, touched, or submitted. */


export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}

Next, open and edit `client/src/app/auth/login/login.component.html` then replace all HTML tags with these.

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<mat-card class="example-card">
<form [formGroup]="loginForm" (ngSubmit)="onFormSubmit(loginForm.value)">
<mat-form-field class="example-full-width">
<input matInput type="email" placeholder="Email" formControlName="username"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!loginForm.get('username').valid && loginForm.get('username').touched">Please enter your username</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput type="password" placeholder="Password" formControlName="password"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!loginForm.get('password').valid && loginForm.get('password').touched">Please enter your password</span>
</mat-error>
</mat-form-field>
<div class="button-row">
<button type="submit" [disabled]="!loginForm.valid" mat-flat-button color="primary">Login</button>
</div>
<div class="button-row">
<button type="button" mat-flat-button color="primary" (click)="register()">Register</button>
</div>
</form>
</mat-card>
</div>

Next, for register page, open and edit `client/src/app/auth/register/register.component.ts` then replace all Typescript codes
with these.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 20/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { AuthService } from '../../auth.service';
import { Router } from '@angular/router';
import { ErrorStateMatcher } from '@angular/material/core';

/** Error when invalid control is dirty, touched, or submitted. */


export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}

@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {

registerForm: FormGroup;
fullName = '';
username = '';
password = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

constructor(private formBuilder: FormBuilder, private router: Router, private authService: AuthService) { }

ngOnInit() {
this.registerForm = this.formBuilder.group({
fullName : [null, Validators.required],
username : [null, Validators.required],
password : [null, Validators.required]
});
}

onFormSubmit(form: NgForm) {
this.authService.register(form)
.subscribe(res => {
this.router.navigate(['login']);
}, (err) => {
console.log(err);
alert(err.error);
});
}

Next, open and edit `client/src/app/auth/register/register.component.html` then replace all HTML tags with these.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 21/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<mat-card class="example-card">
<form [formGroup]="registerForm" (ngSubmit)="onFormSubmit(registerForm.value)">
<mat-form-field class="example-full-width">
<input matInput type="fullName" placeholder="Full Name" formControlName="fullName"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!registerForm.get('fullName').valid && registerForm.get('fullName').touched">Please enter your Full Name</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput type="email" placeholder="Email" formControlName="username"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!registerForm.get('username').valid && registerForm.get('username').touched">Please enter your username</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput type="password" placeholder="Password" formControlName="password"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!registerForm.get('password').valid && registerForm.get('password').touched">Please enter your password</span>
</mat-error>
</mat-form-field>
<div class="button-row">
<button type="submit" [disabled]="!registerForm.valid" mat-flat-button color="primary">Register</button>
</div>
</form>
</mat-card>
</div>

Add Angular Material Blog Category CRUD Component


The blog category is part of the secure Angular component. We will add Angular Material CRUD components for this. The way
to build this complete CRUD component is almost the same as previous steps on Angular login and register. So, we will show
you directly through the source codes. Begin with the list of categories, open and edit
`src/app/category/category.component.ts` then add these lines of codes. There should be a function to load the list of
Categories that put to data array and declare a loading spinner and fields selection for the Angular Material table.

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


import { CategoryService } from '../category.service';
import { Category } from './category';

@Component({
selector: 'app-category',
templateUrl: './category.component.html',
styleUrls: ['./category.component.scss']
})
export class CategoryComponent implements OnInit {

displayedColumns: string[] = ['catName', 'catDesc'];


data: Category[] = [];
isLoadingResults = true;

constructor(private api: CategoryService) { }

ngOnInit() {
this.api.getCategories()
.subscribe((res: any) => {
this.data = res;
console.log(this.data);
this.isLoadingResults = false;
}, err => {
console.log(err);
this.isLoadingResults = false;
});
}

Next, open and edit `src/app/category/category.component.html` then replace all HTML tags with these Angular Material
components that contain a table to display a list of categories.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 22/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="button-row">
<a mat-flat-button color="primary" [routerLink]="['add']"><mat-icon>add</mat-icon></a>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="data" class="example-table"
matSort matSortActive="catName" matSortDisableClear matSortDirection="asc">

<!-- Category Name Column -->


<ng-container matColumnDef="catName">
<th mat-header-cell *matHeaderCellDef>Category Name</th>
<td mat-cell *matCellDef="let row">{{row.catName}}</td>
</ng-container>

<!-- Category Description Column -->


<ng-container matColumnDef="catDesc">
<th mat-header-cell *matHeaderCellDef>Category Description</th>
<td mat-cell *matCellDef="let row">{{row.catDesc}}</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>


<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['details/', row._id]"></tr>
</table>
</div>
</div>

Next, open and edit `src/app/category/category-details/category-details.component.ts` then replace all Typescript codes with
these codes that contain the functions for load a single category by ID and delete a category by ID. The loaded category data
hold put to category type data object.

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


import { ActivatedRoute, Router } from '@angular/router';
import { CategoryService } from './../../category.service';
import { Category } from '../category';

@Component({
selector: 'app-category-details',
templateUrl: './category-details.component.html',
styleUrls: ['./category-details.component.scss']
})
export class CategoryDetailsComponent implements OnInit {

category: Category = { id: null, catName: '', catDesc: '', catImgUrl: '', catContent: '', updated: null };
isLoadingResults = true;

constructor(private route: ActivatedRoute, private api: CategoryService, private router: Router) { }

ngOnInit() {
this.getCategoryDetails(this.route.snapshot.params.id);
}

getCategoryDetails(id: any) {
this.api.getCategory(id)
.subscribe((data: any) => {
this.category = data;
console.log(this.category);
this.isLoadingResults = false;
});
}

deleteCategory(id: any) {
this.isLoadingResults = true;
this.api.deleteCategory(id)
.subscribe(res => {
this.isLoadingResults = false;
this.router.navigate(['/category']);
}, (err) => {
console.log(err);
this.isLoadingResults = false;
}
);
}

Next, open and edit `src/app/category/category-details/category-details.component.html` then replace all HTML tags with
these Angular Material components that contain a card that displays category details.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 23/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="button-row">
<a mat-flat-button color="primary" [routerLink]="['/category']"><mat-icon>list</mat-icon></a>
</div>
<mat-card class="example-card">
<mat-card-header>
<mat-card-title><h2>{{category.catName}}</h2></mat-card-title>
<mat-card-subtitle>{{category.catDesc}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="{{category.catImgUrl}}" alt="{{category.catName}}">
<mat-card-content>
<dl>
<dt>Category Content:</dt>
<dd [innerHTML]="category.catContent"></dd>
<dt>Updated At:</dt>
<dd>{{category.updated | date}}</dd>
</dl>
</mat-card-content>
<mat-card-actions>
<a mat-flat-button color="primary" [routerLink]="['/category/edit', category?.id || 'all']"><mat-icon>edit</mat-icon></a>
<a mat-flat-button color="warn" (click)="deleteCategory(category.id)"><mat-icon>delete</mat-icon></a>
</mat-card-actions>
</mat-card>
</div>

For add and edit category form we will need a Rich Text editor. For this, we will install CKEditor that fortunately supported
Angular. To install it simply run this command.

npm install --save ckeditor4-angular


npm install mat-contenteditable --save

Next, open and edit `src/app/app.module.ts` then add this import.

import { CKEditorModule } from 'ckeditor4-angular';


import { MatContenteditableModule } from 'mat-contenteditable';

Add to the @NgModule imports.

imports: [
...
CKEditorModule,
MatContenteditableModule
],

Next, open and edit `src/app/category/category-add/category-add.component.ts` then replace all Typescript codes with these
codes that contain the Angular Form, FormBuilder, FormGroup, FormControl, FormGroupDirective, Validators,
ErrorStateMatcher, etc. The Angular FormGroup initialize by the FormBuilder then the passed validation form fields can submit
to the REST API.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 24/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { Router } from '@angular/router';
import { CategoryService } from '../../category.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

/** Error when invalid control is dirty, touched, or submitted. */


export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}

@Component({
selector: 'app-category-add',
templateUrl: './category-add.component.html',
styleUrls: ['./category-add.component.scss']
})
export class CategoryAddComponent implements OnInit {

categoryForm: FormGroup;
catName = '';
catDesc = '';
catImgUrl = '';
catContent = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

constructor(private router: Router, private api: CategoryService, private formBuilder: FormBuilder) { }

ngOnInit() {
this.categoryForm = this.formBuilder.group({
catName : [null, Validators.required],
catDesc : [null, Validators.required],
catImgUrl : [null, Validators.required],
catContent : [null, Validators.required]
});
}

onFormSubmit() {
this.isLoadingResults = true;
this.api.addCategory(this.categoryForm.value)
.subscribe((res: any) => {
const id = res._id;
this.isLoadingResults = false;
this.router.navigate(['/category-details', id]);
}, (err: any) => {
console.log(err);
this.isLoadingResults = false;
});
}

Next, open and edit `src/app/category/category-add/category-add.component.html` then replace all HTML tags with these
Angular Material components that contain Angular FormGroup, FormControl, Material Input, Textarea, and Additional CKEditor
4 that previously installs.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 25/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="button-row">
<a mat-flat-button color="primary" [routerLink]="['/category']"><mat-icon>list</mat-icon></a>
</div>
<mat-card class="example-card">
<form [formGroup]="categoryForm" (ngSubmit)="onFormSubmit()">
<mat-form-field class="example-full-width">
<input matInput placeholder="Category Name" formControlName="catName"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!categoryForm.get('catName').valid && categoryForm.get('catName').touched">Please enter Category Name</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<textarea matInput placeholder="Category Desc" formControlName="catDesc"
[errorStateMatcher]="matcher"></textarea>
<mat-error>
<span *ngIf="!categoryForm.get('catDesc').valid && categoryForm.get('catDesc').touched">Please enter Category Description</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Category Image URL" formControlName="catImgUrl"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!categoryForm.get('catImgUrl').valid && categoryForm.get('catImgUrl').touched">Please enter Category Image URL</span
>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<ckeditor matCkeditor placehoder="Category Content" formControlName="catContent"></ckeditor>
<mat-error>
<span *ngIf="!categoryForm.get('catContent').valid && categoryForm.get('catContent').touched">Please enter Category Description
</span>
</mat-error>
</mat-form-field>
<div class="button-row">
<button type="submit" [disabled]="!categoryForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
</div>
</form>
</mat-card>
</div>

Next, open and edit `src/app/category/category-edit/category-edit.component.ts` then replace all Typescript codes with these
codes that contain the Angular Form, FormBuilder, FormGroup, FormControl, FormGroupDirective, Validators,
ErrorStateMatcher, etc. The Angular FormGroup initialize by the FormBuilder and fill with the category that loaded by getting
category function then the passed validation form fields can submit to the REST API.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 26/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { Router, ActivatedRoute } from '@angular/router';
import { CategoryService } from '../../category.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';

/** Error when invalid control is dirty, touched, or submitted. */


export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}

@Component({
selector: 'app-category-edit',
templateUrl: './category-edit.component.html',
styleUrls: ['./category-edit.component.scss']
})
export class CategoryEditComponent implements OnInit {

categoryForm: FormGroup;
id = '';
catName = '';
catDesc = '';
catImgUrl = '';
catContent = '';
updated: Date = null;
isLoadingResults = false;
matcher = new MyErrorStateMatcher();

constructor(private router: Router, private route: ActivatedRoute, private api: CategoryService, private formBuilder: FormBuilder) { }

ngOnInit() {
this.getCategory(this.route.snapshot.params.id);
this.categoryForm = this.formBuilder.group({
catName : [null, Validators.required],
catDesc : [null, Validators.required],
catImgUrl : [null, Validators.required],
catContent : [null, Validators.required]
});
}

getCategory(id: any) {
this.api.getCategory(id).subscribe((data: any) => {
this.id = data.id;
this.categoryForm.setValue({
prod_name: data.prod_name,
prod_desc: data.prod_desc,
prod_price: data.prod_price
});
});
}

onFormSubmit() {
this.isLoadingResults = true;
this.api.updateCategory(this.id, this.categoryForm.value)
.subscribe((res: any) => {
const id = res.id;
this.isLoadingResults = false;
this.router.navigate(['/category-details', id]);
}, (err: any) => {
console.log(err);
this.isLoadingResults = false;
}
);
}

categoryDetails() {
this.router.navigate(['/category-details', this.id]);
}

Next, open and edit `src/app/category/category-edit/category-edit.component.html` then replace all HTML tags with these
Angular Material components that contain Angular FormGroup, FormControl, Material Input, Textarea, and Additional CKEditor
4 that previously installs.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 27/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="button-row">
<a mat-flat-button color="primary" (click)="categoryDetails()"><mat-icon>list</mat-icon></a>
</div>
<mat-card class="example-card">
<form [formGroup]="categoryForm" (ngSubmit)="onFormSubmit()">
<mat-form-field class="example-full-width">
<input matInput placeholder="Category Name" formControlName="catName"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!categoryForm.get('catName').valid && categoryForm.get('catName').touched">Please enter Category Name</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<textarea matInput placeholder="Category Desc" formControlName="catDesc"
[errorStateMatcher]="matcher"></textarea>
<mat-error>
<span *ngIf="!categoryForm.get('catDesc').valid && categoryForm.get('catDesc').touched">Please enter Category Description</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Category Image URL" formControlName="catImgUrl"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!categoryForm.get('catImgUrl').valid && categoryForm.get('catImgUrl').touched">Please enter Category Image URL</span
>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<ckeditor matCkeditor placehoder="Category Content" formControlName="catContent"></ckeditor>
<mat-error>
<span *ngIf="!categoryForm.get('catContent').valid && categoryForm.get('catContent').touched">Please enter Category Description</
span>
</mat-error>
</mat-form-field>
<div class="button-row">
<button type="submit" [disabled]="!categoryForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
</div>
</form>
</mat-card>
</div>

Add Angular Material Blog Post CRUD Component


The blog post is part of the secure Angular component. We will add Angular Material CRUD components for this. The way to
build this complete CRUD component is same as previous steps on the blog post. So, we will show you directly through the
source codes. Begin with the list of post, open and edit `src/app/post/post.component.ts` then add these lines of codes. There
should be a function to load the list of posts that put to data array and declare a loading spinner and fields selection for the
Angular Material table.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 28/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { PostService } from '../post.service';
import { Post } from './post';

@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.scss']
})
export class PostComponent implements OnInit {

displayedColumns: string[] = ['postTitle', 'postDesc'];


data: Post[] = [];
isLoadingResults = true;

constructor(private api: PostService) { }

ngOnInit() {
this.api.getPosts()
.subscribe((res: any) => {
this.data = res;
console.log(this.data);
this.isLoadingResults = false;
}, err => {
console.log(err);
this.isLoadingResults = false;
});
}

Next, open and edit `src/app/post/post.component.html` then replace all HTML tags with these Angular Material components
that contain a table to display a list of posts.

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="button-row">
<a mat-flat-button color="primary" [routerLink]="['add']"><mat-icon>add</mat-icon></a>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="data" class="example-table"
matSort matSortActive="postTitle" matSortDisableClear matSortDirection="asc">

<!-- Post Name Column -->


<ng-container matColumnDef="postTitle">
<th mat-header-cell *matHeaderCellDef>Post Title</th>
<td mat-cell *matCellDef="let row">{{row.postTitle}}</td>
</ng-container>

<!-- Post Description Column -->


<ng-container matColumnDef="postDesc">
<th mat-header-cell *matHeaderCellDef>Post Description</th>
<td mat-cell *matCellDef="let row">{{row.postDesc}}</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>


<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['details/', row._id]"></tr>
</table>
</div>
</div>

Next, open and edit `src/app/post/post-details/post-details.component.ts` then replace all Typescript codes with these codes
that contain the functions for load a single post by ID and delete a post by ID. The loaded post data hold put to post type data
object.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 29/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { ActivatedRoute, Router } from '@angular/router';
import { PostService } from './../../post.service';
import { Post } from '../post';

@Component({
selector: 'app-post-details',
templateUrl: './post-details.component.html',
styleUrls: ['./post-details.component.scss']
})
export class PostDetailsComponent implements OnInit {

post: Post = {
category: '',
id: '',
postTitle: '',
postAuthor: '',
postDesc: '',
postContent: '',
postReference: '',
postImgUrl: '',
created: null,
updated: null
};
isLoadingResults = true;

constructor(private route: ActivatedRoute, private api: PostService, private router: Router) { }

ngOnInit() {
this.getPostDetails(this.route.snapshot.params.id);
}

getPostDetails(id: any) {
this.api.getPost(id)
.subscribe((data: any) => {
this.post = data;
this.post.id = data._id;
console.log(this.post);
this.isLoadingResults = false;
});
}

deletePost(id: any) {
this.isLoadingResults = true;
this.api.deletePost(id)
.subscribe(res => {
this.isLoadingResults = false;
this.router.navigate(['/post']);
}, (err) => {
console.log(err);
this.isLoadingResults = false;
}
);
}

Next, open and edit `src/app/post/post-details/post-details.component.html` then replace all HTML tags with these Angular
Material components that contain a card that displays post details.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 30/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="button-row">
<a mat-flat-button color="primary" [routerLink]="['/post']"><mat-icon>list</mat-icon></a>
</div>
<mat-card class="example-card">
<mat-card-header>
<mat-card-title><h2>{{post.postTitle}}</h2></mat-card-title>
<p>Created by: {{post.postAuthor}}, {{post.created | date}}, updated: {{post.updated | date}}</p>
<mat-card-subtitle>{{post.postDesc}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="{{post.postImgUrl}}" alt="{{post.postTitle}}">
<mat-card-content>
<dl>
<dt>Post Content:</dt>
<dd [innerHTML]="post.postContent"></dd>
<dt>Reference:</dt>
<dd>{{post.postReference}}</dd>
</dl>
</mat-card-content>
<mat-card-actions>
<a mat-flat-button color="primary" [routerLink]="['/post/edit', post.id]"><mat-icon>edit</mat-icon></a>
<a mat-flat-button color="warn" (click)="deletePost(post.id)"><mat-icon>delete</mat-icon></a>
</mat-card-actions>
</mat-card>
</div>

Next, open and edit `src/app/post/post-add/post-add.component.ts` then replace all Typescript codes with these codes that
contain the Angular Form, FormBuilder, FormGroup, FormControl, FormGroupDirective, Validators, ErrorStateMatcher, etc. The
Angular FormGroup initialize by the FormBuilder then the passed validation form fields can submit to the REST API.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 31/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { Router } from '@angular/router';
import { PostService } from '../../post.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import { CategoryService } from '../../category.service';
import { Category } from './../../category/category';

/** Error when invalid control is dirty, touched, or submitted. */


export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}

@Component({
selector: 'app-post-add',
templateUrl: './post-add.component.html',
styleUrls: ['./post-add.component.scss']
})
export class PostAddComponent implements OnInit {

postForm: FormGroup;
category = '';
postTitle = '';
postAuthor = '';
postDesc = '';
postContent = '';
postReference = '';
postImgUrl = '';
isLoadingResults = false;
matcher = new MyErrorStateMatcher();
categories: Category[] = [];

constructor(
private router: Router,
private api: PostService,
private catApi: CategoryService,
private formBuilder: FormBuilder) { }

ngOnInit() {
this.getCategories();
this.postForm = this.formBuilder.group({
category : [null, Validators.required],
postTitle : [null, Validators.required],
postAuthor : [null, Validators.required],
postDesc : [null, Validators.required],
postContent : [null, Validators.required],
postReference : [null, Validators.required],
postImgUrl : [null, Validators.required]
});
}

onFormSubmit() {
this.isLoadingResults = true;
this.api.addPost(this.postForm.value)
.subscribe((res: any) => {
const id = res._id;
this.isLoadingResults = false;
this.router.navigate(['/post/details', id]);
}, (err: any) => {
console.log(err);
this.isLoadingResults = false;
});
}

getCategories() {
this.catApi.getCategories()
.subscribe((res: any) => {
this.categories = res;
console.log(this.categories);
this.isLoadingResults = false;
}, err => {
console.log(err);
this.isLoadingResults = false;
});
}

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 32/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
Next, open and edit `src/app/post/post-add/post-add.component.html` then replace all HTML tags with these Angular Material
components that contain Angular FormGroup, FormControl, Material Input, Textarea, and Additional CKEditor 4 that previously
installs.

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="button-row">
<a mat-flat-button color="primary" [routerLink]="['/post']"><mat-icon>list</mat-icon></a>
</div>
<mat-card class="example-card">
<form [formGroup]="postForm" (ngSubmit)="onFormSubmit()">
<mat-form-field class="example-full-width">
<mat-select formControlName="category" [errorStateMatcher]="matcher">
<mat-option *ngFor="let cat of categories" [value]="cat._id">
{{cat.catName}}
</mat-option>
</mat-select>
<mat-error>
<span *ngIf="!postForm.get('category').valid && postForm.get('category').touched">Please select Category</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Post Title" formControlName="postTitle"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!postForm.get('postTitle').valid && postForm.get('postTitle').touched">Please enter Post Title</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Post Author" formControlName="postAuthor"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!postForm.get('postAuthor').valid && postForm.get('postAuthor').touched">Please enter Post Author</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<textarea matInput placeholder="Post Desc" formControlName="postDesc"
[errorStateMatcher]="matcher"></textarea>
<mat-error>
<span *ngIf="!postForm.get('postDesc').valid && postForm.get('postDesc').touched">Please enter Post Description</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<ckeditor matCkeditor placehoder="Post Content" formControlName="postContent"></ckeditor>
<mat-error>
<span *ngIf="!postForm.get('postContent').valid && postForm.get('postContent').touched">Please enter Post Content</span>
</mat-error>
</mat-form-field>
<div class="button-row">
<mat-form-field class="example-full-width">
<input matInput placeholder="Post Reference" formControlName="postReference"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!postForm.get('postReference').valid && postForm.get('postReference').touched">Please enter Post Ref</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Post Image URL" formControlName="postImgUrl"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!postForm.get('postImgUrl').valid && postForm.get('postImgUrl').touched">Please enter Post Image URL</span>
</mat-error>
</mat-form-field>
<button type="submit" [disabled]="!postForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
</div>
</form>
</mat-card>
</div>

Next, open and edit `src/app/post/post-edit/post-edit.component.ts` then replace all Typescript codes with these codes that
contain the Angular Form, FormBuilder, FormGroup, FormControl, FormGroupDirective, Validators, ErrorStateMatcher, etc. The
Angular FormGroup initialize by the FormBuilder and fill with the post that loaded by getting post function then the passed
validation form fields can submit to the REST API.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 33/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { Router, ActivatedRoute } from '@angular/router';
import { PostService } from '../../post.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import { CategoryService } from '../../category.service';
import { Category } from './../../category/category';

/** Error when invalid control is dirty, touched, or submitted. */


export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}

@Component({
selector: 'app-post-edit',
templateUrl: './post-edit.component.html',
styleUrls: ['./post-edit.component.scss']
})
export class PostEditComponent implements OnInit {

postForm: FormGroup;
category = '';
id = '';
postTitle = '';
postAuthor = '';
postDesc = '';
postContent = '';
postReference = '';
postImgUrl = '';
updated: Date = null;
isLoadingResults = false;
matcher = new MyErrorStateMatcher();
categories: Category[] = [];

constructor(
private router: Router,
private route: ActivatedRoute,
private api: PostService,
private catApi: CategoryService,
private formBuilder: FormBuilder) { }

ngOnInit() {
this.getCategories();
this.getPost(this.route.snapshot.params.id);
this.postForm = this.formBuilder.group({
postTitle : [null, Validators.required],
postAuthor : [null, Validators.required],
postDesc : [null, Validators.required],
postContent : [null, Validators.required],
postReference : [null, Validators.required],
postImgUrl : [null, Validators.required]
});
}

getPost(id: any) {
this.api.getPost(id).subscribe((data: any) => {
this.id = data.id;
this.postForm.setValue({
postTitle: data.postTitle,
postAuthor: data.postAuthor,
postDesc: data.postDesc,
postContent: data.postContent,
postReference: data.postReference,
postImgUrl: data.postImgUrl
});
});
}

getCategories() {
this.catApi.getCategories()
.subscribe((res: any) => {
this.categories = res;
console.log(this.categories);
this.isLoadingResults = false;
}, err => {
console.log(err);
this.isLoadingResults = false;
});
}

onFormSubmit() {

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 34/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
this.isLoadingResults = true;
this.api.updatePost(this.id, this.postForm.value)
.subscribe((res: any) => {
const id = res.id;
this.isLoadingResults = false;
this.router.navigate(['/post-details', id]);
}, (err: any) => {
console.log(err);
this.isLoadingResults = false;
}
);
}

postDetails() {
this.router.navigate(['/post-details', this.id]);
}

Next, open and edit `src/app/post/post-edit/post-edit.component.html` then replace all HTML tags with these Angular Material
components that contain Angular FormGroup, FormControl, Material Input, Textarea, and Additional CKEditor 4 that previously
installs.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 35/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<div class="button-row">
<a mat-flat-button color="primary" (click)="postDetails()"><mat-icon>list</mat-icon></a>
</div>
<mat-card class="example-card">
<form [formGroup]="postForm" (ngSubmit)="onFormSubmit()">
<mat-form-field class="example-full-width">
<mat-select formControlName="category" [errorStateMatcher]="matcher">
<mat-option *ngFor="let cat of categories" [value]="cat.id">
{{cat.catName}}
</mat-option>
</mat-select>
<mat-error>
<span *ngIf="!postForm.get('category').valid && postForm.get('category').touched">Please select Category</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Post Title" formControlName="postTitle"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!postForm.get('postTitle').valid && postForm.get('postTitle').touched">Please enter Post Title</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Post Author" formControlName="postAuthor"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!postForm.get('postAuthor').valid && postForm.get('postAuthor').touched">Please enter Post Author</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<textarea matInput placeholder="Post Desc" formControlName="postDesc"
[errorStateMatcher]="matcher"></textarea>
<mat-error>
<span *ngIf="!postForm.get('postDesc').valid && postForm.get('postDesc').touched">Please enter Post Description</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<ckeditor matInput placehoder="Post Content" formControlName="postContent"></ckeditor>
<mat-error>
<span *ngIf="!postForm.get('postContent').valid && postForm.get('postContent').touched">Please enter Post Content</span>
</mat-error>
</mat-form-field>
<div class="button-row">
<mat-form-field class="example-full-width">
<input matInput placeholder="Post Reference" formControlName="postReference"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!postForm.get('postReference').valid && postForm.get('postReference').touched">Please enter Post Ref</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Post Image URL" formControlName="catImgUrl"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!postForm.get('catImgUrl').valid && postForm.get('catImgUrl').touched">Please enter Post Image URL</span>
</mat-error>
</mat-form-field>
<button type="submit" [disabled]="!postForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
</div>
</form>
</mat-card>
</div>

Secure the Components using Angular 8 Route Guard


As we mention in the beginning that the Angular 8 application will use Angular 8 Route Guard to secure the category and post
page. So, we have both securities for the Angular 8 component and for Node Express REST API. Type this command to
generate a guard configuration file.

ng generate guard auth/auth

Open and edit that file and there are already Angular 8 or Typescript imports of the @angular/router CanActivate,
ActivatedRouteSnapshot, and RouterStateSnapshot. So, just these imports of AuthService and Angular Router.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 36/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

import { AuthService } from '../auth.service';

Inject the `AuthService` and the `Router` to the constructor params.

constructor(private authService: AuthService, private router: Router) {}

Replace a generated function of canActivate with this function.


Add the function for the Route Guard.

canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
const url: string = state.url;

return this.checkLogin(url);
}

Add the function to check the login status and redirect to the login page if it's not logged in and redirect to the Guarded page if
it's logged in.

checkLogin(url: string): boolean {


if (this.authService.loggedInStatus) { return true; }

// Store the attempted URL for redirecting


this.authService.redirectUrl = url;

// Navigate to the login page with extras


this.router.navigate(['/login']);
return false;
}

Next, open and edit `src/app/app-routing.module.ts` then add this import.

import { AuthGuard } from './auth/auth.guard';

Modify the paths that should be secure, so it will look like this.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 37/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

const routes: Routes = [


{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: 'home',
component: HomeComponent,
data: { title: 'Blog Home' }
},
{
path: 'admin',
canActivate: [AuthGuard],
component: AdminComponent,
data: { title: 'Blog Admin' }
},
{
path: 'bycategory/:id',
component: BycategoryComponent,
data: { title: 'Post by Category' }
},
{
path: 'details/:id',
component: DetailsComponent,
data: { title: 'Show Post Details' }
},
{
path: 'login',
component: LoginComponent,
data: { title: 'Login' }
},
{
path: 'register',
component: RegisterComponent,
data: { title: 'Register' }
},
{
path: 'category',
canActivate: [AuthGuard],
component: CategoryComponent,
data: { title: 'Category' }
},
{
path: 'category/details/:id',
canActivate: [AuthGuard],
component: CategoryDetailsComponent,
data: { title: 'Category Details' }
},
{
path: 'category/add',
canActivate: [AuthGuard],
component: CategoryAddComponent,
data: { title: 'Category Add' }
},
{
path: 'category/edit/:id',
canActivate: [AuthGuard],
component: CategoryEditComponent,
data: { title: 'Category Edit' }
},
{
path: 'post',
canActivate: [AuthGuard],
component: PostComponent,
data: { title: 'Post' }
},
{
path: 'post/details/:id',
canActivate: [AuthGuard],
component: PostDetailsComponent,
data: { title: 'Post Details' }
},
{
path: 'post/add',
canActivate: [AuthGuard],
component: PostAddComponent,
data: { title: 'Post Add' }
},
{
path: 'post/edit/:id',
canActivate: [AuthGuard],
component: PostEditComponent,
data: { title: 'Post Edit' }

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 38/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
}
];

Add Angular Material Blog Front Page


The main feature of the Blog website or application is the front page or home page. We will put a navigation toolbar and menu
that contain the categories. The menu content depends on the logged-in status. If the user logged in then the menu should
contain category and post. Otherwise, the menu just contains home and categories. To achieve that, open and edit
`src/app/app.component.ts` then add these imports of category type, home, and auth service.

import { Router } from '@angular/router';


import { Category } from './category/category';
import { HomeService } from './home.service';
import { AuthService } from './auth.service';

Inject that home and auth service to the constructor by adding a constructor if it doesn't exist.

constructor(private api: HomeService, private authService: AuthService, private router: Router) { }

Add these variables that hold the categories array and the status of the logged-in user.

categories: Category[] = [];


loginStatus = false;

Put the logged-in status check inside the NgOnInit function followed by load categories from the REST API.

ngOnInit() {
this.authService.isLoggedIn.subscribe((status: any) => {
if (status === true) {
this.loginStatus = true;
} else {
this.loginStatus = false;
}
});
this.api.getCategories()
.subscribe((res: any) => {
this.categories = res;
console.log(this.categories);
}, err => {
console.log(err);
});
}

Add a function to logout the application.

logout() {
this.authService.logout()
.subscribe((res: any) => {
this.router.navigate(['/']);
}, err => {
console.log(err);
});
}

If there's no ngOnInit in this app.component.ts, add the import for it and add implements to the class name.

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


export class AppComponent implements OnInit {
...
}

Next, open and edit `src/app/app.component.html` then replace all of the HTML tags with these.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 39/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

<mat-toolbar>
<button mat-button [matMenuTriggerFor]="menu">Menu</button>
<mat-menu #menu="matMenu">
<div *ngIf="loginStatus === false">
<button mat-menu-item [routerLink]="['/']">Home</button>
<button mat-menu-item *ngFor="let cat of categories" [routerLink]="['/bycategory/', cat._id]">{{cat.catName}}</button>
</div>
<div *ngIf="loginStatus === true">
<button mat-menu-item [routerLink]="['/admin']">Home</button>
<button mat-menu-item [routerLink]="['/category']">Category</button>
<button mat-menu-item [routerLink]="['/post']">Post</button>
<button mat-menu-item (click)="logout()">Logout</button>
</div>
</mat-menu>
</mat-toolbar>
<router-outlet></router-outlet>

Next, we will show the list of post on the home page. For that, open and edit `src/app/home/home.component.ts` then replace
all Typescript codes with these codes that contain a function to load post data.

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


import { Post } from '../post/post';
import { HomeService } from '../home.service';

@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

posts: Post[] = [];


isLoadingResults = true;

constructor(private api: HomeService) { }

ngOnInit() {
this.api.getPosts()
.subscribe((res: any) => {
this.posts = res;
console.log(this.posts);
this.isLoadingResults = false;
}, err => {
console.log(err);
this.isLoadingResults = false;
});
}

Next, open and edit `src/app/home/home.component.html` then replace with these HTML tags that contain the Angular
Material component to display the grid of data.

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<mat-grid-list cols="3">
<mat-grid-tile *ngFor="let post of posts">
<mat-card class="example-card" [routerLink]="['/details/', post._id]">
<mat-card-header>
<div mat-card-avatar class="example-header-image"></div>
<mat-card-title>{{post.postTitle}}</mat-card-title>
<mat-card-subtitle>{{post.updated}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="{{post.postImgUrl}}" alt="Photo of a Shiba Inu">
<mat-card-content>
{{post.postDesc}}
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</div>

Next, to display the post details, open and edit `src/app/details/details.component.ts` then add these lines of Typescript codes
that contain Post objects variable and the call of REST API to get the Post data by ID.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 40/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { ActivatedRoute, Router } from '@angular/router';
import { Post } from '../post/post';
import { HomeService } from '../home.service';

@Component({
selector: 'app-details',
templateUrl: './details.component.html',
styleUrls: ['./details.component.scss']
})
export class DetailsComponent implements OnInit {

post: Post = {
category: '',
id: '',
postTitle: '',
postAuthor: '',
postDesc: '',
postContent: '',
postReference: '',
postImgUrl: '',
created: null,
updated: null
};
isLoadingResults = true;

constructor(private route: ActivatedRoute, private api: HomeService, private router: Router) { }

ngOnInit() {
this.getPostDetails(this.route.snapshot.params.id);
}

getPostDetails(id: any) {
this.api.getPost(id)
.subscribe((data: any) => {
this.post = data;
console.log(this.post);
this.isLoadingResults = false;
});
}

Next, open and edit `src/app/details/details.component.html` then replace all HTML tags with these Angular Materials Card
component that contain the post details.

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<mat-card class="example-card" [routerLink]="['/details/', post.id]">
<mat-card-header>
<div mat-card-avatar class="example-header-image"></div>
<mat-card-title>{{post.postTitle}}</mat-card-title>
<p>By: {{post.postAuthor}}, {{post.updated | date: 'dd MMM yyyy'}}</p>
<mat-card-subtitle>{{post.postDesc}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="{{post.postImgUrl}}" alt="{{post.postTitle}}">
<mat-card-content [innerHTML]="post.postContent"></mat-card-content>
<mat-card-actions>
<button mat-button>LIKE</button>
<button mat-button>SHARE</button>
</mat-card-actions>
</mat-card>
</div>

Next, we need to add the page that displays the list of the post by category. This page is the action for the categories menu.
Open and edit `src/app/bycategory/bycategory.component.ts` then replace all Typescript codes with these codes that contain
a function to load post data by category.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 41/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

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


import { ActivatedRoute, Router } from '@angular/router';
import { Post } from '../post/post';
import { HomeService } from '../home.service';

@Component({
selector: 'app-bycategory',
templateUrl: './bycategory.component.html',
styleUrls: ['./bycategory.component.scss']
})
export class BycategoryComponent implements OnInit {

posts: Post[] = [];


isLoadingResults = true;

constructor(private route: ActivatedRoute, private api: HomeService) { }

ngOnInit() {
this.route.params.subscribe(params => {
this.getPostsByCategory(this.route.snapshot.params.id);
});
}

getPostsByCategory(id: any) {
this.posts = [];
this.api.getPostsByCategory(id)
.subscribe((res: any) => {
this.posts = res;
console.log(this.posts);
this.isLoadingResults = false;
}, err => {
console.log(err);
this.isLoadingResults = false;
});
}

Next, open and edit `src/app/bycategory/bycategory.component.html` then replace all HTML tags with these Angular Material
Grid component to display the list of the post by category.

<div class="example-container mat-elevation-z8">


<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<mat-grid-list cols="3">
<mat-grid-tile *ngFor="let post of posts">
<mat-card class="example-card" [routerLink]="['/details/', post._id]">
<mat-card-header>
<div mat-card-avatar class="example-header-image"></div>
<mat-card-title>{{post.postTitle}}</mat-card-title>
<mat-card-subtitle>{{post.updated}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="{{post.postImgUrl}}" alt="Photo of a Shiba Inu">
<mat-card-content>
{{post.postDesc}}
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</div>

Finally, we will adjust the style for all Angular Material component globally by open and edit `src/styles.scss` then add these
lines of SCSS codes.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 42/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

.example-container {
position: relative;
padding: 5px;
}

.example-table-container {
position: relative;
max-height: 400px;
overflow: auto;
}

table {
width: 100%;
}

.example-loading-shade {
position: absolute;
top: 0;
left: 0;
bottom: 56px;
right: 0;
background: rgba(0, 0, 0, 0.15);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}

.example-rate-limit-reached {
color: #980000;
max-width: 360px;
text-align: center;
}

/* Column Widths */
.mat-column-number,
.mat-column-state {
max-width: 64px;
}

.mat-column-created {
max-width: 124px;
}

.mat-flat-button {
margin: 5px;
}

.example-container {
position: relative;
padding: 5px;
}

.example-form {
min-width: 150px;
max-width: 500px;
width: 100%;
}

.example-full-width {
width: 100%;
}

.example-full-width:nth-last-child(0) {
margin-bottom: 10px;
}

.button-row {
margin: 10px 0;
}

.mat-flat-button {
margin: 5px;
}

Run and Test the MEAN Stack (Angular 8) Blog CMS


Now, we use another way to run the MEAN Stack (Angular 8). In our previous MEAN stack tutorial, we run the Express.js and
Angular in the same server. But now, we run them separately to make development easier and lightweight. For that, we need to
open 3 terminal tabs to run MongoDB daemon, Express.js, and Angular 8. Run this command in the first terminal tab.

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 43/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

mongod

In the second terminal tab inside the Blog-CMS folder run this command.

nodemon

In the third terminal tab inside the blog-cms/client folder run this command.

ng serve

And here the full demo of the MEAN Stack (Angular 8) tutorial from our YouTube Channel.

MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS …

That it's, the MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS. You can find the full working source codes from our
GitHub (https://github.com/didinj/mean-stack-angular-8-blog-cms.git).

If you don’t want to waste your time design your own front-end or your budget to spend by hiring a web designer then Angular
Templates is the best place to go. So, speed up your front-end web development with premium Angular templates
(https://angular-templates.io?ref=8). Choose your template for your front-end project here (https://angular-templates.io?ref=8).

That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can take the following cheap
course:
Master en JavaScript: Aprender JS, jQuery, Angular 8, NodeJS (https://click.linksynergy.com/link?
id=6nYo96*QrJE&offerid=507388.1337000&type=2&murl=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Fmaster-en-
javascript-aprender-js-jquery-angular-nodejs-y-mas%2F)
Angular 8 - Complete Essential Guide (https://click.linksynergy.com/link?
id=6nYo96*QrJE&offerid=507388.2159182&type=2&murl=https%3A%2F%2Fwww.udemy.com%2Fangular-7-complete-
essential-guide%2F)
Learn Angular 8 by creating a simple Full Stack Web App (https://click.linksynergy.com/link?
id=6nYo96*QrJE&offerid=507388.2400782&type=2&murl=https%3A%2F%2Fwww.udemy.com%2Fangular-8%2F)
Angular 5 Bootcamp FastTrack (https://click.linksynergy.com/link?
id=6nYo96*QrJE&offerid=358574.1461406&type=2&murl=https%3A%2F%2Fwww.udemy.com%2Fangular-bootcamp-
fasttrack%2F)
Angular 6 - Soft & Sweet (https://click.linksynergy.com/link?
id=6nYo96*QrJE&offerid=507388.1668070&type=2&murl=https%3A%2F%2Fwww.udemy.com%2Fangular-soft-and-
sweet%2F)
Angular 6 with TypeScript (https://click.linksynergy.com/link?
id=6nYo96*QrJE&offerid=507388.1757238&type=2&murl=https%3A%2F%2Fwww.udemy.com%2Fangular6withtypescript%2F)
Thanks!

Follow

(http://www.facebook.com/djamwarecom)

(http://twitter.com/intent/follow?source=followbutton&variant=1.0&screen_name=djamware)

(http://www.pinterest.com/djamware) (http://www.linkedin.com/in/didin-jamaludin-7a530351)

(http://www.youtube.com/channel/UCtI81hYLh2Ae_45KHkyy0vw?sub_confirmation=1) (https://github.com/didinj)

 Previous Article
Build a web app using Nestjs, Fastify, MongoDB and Angular 8
(/post/5d2898430707cc5968d9d57f/build-a-web-app-using-nestjs-fastify-mongodb-and-angular-8)

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 44/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

Next Article 
MEAN Stack Angular 9 Build Realtime CRUD Web App Quickly
(/post/5e50b735525fc968b04a707f/mean-stack-angular-9-build-realtime-crud-web-app-quickly)

Related Articles
MEAN Stack Angular 9 Build Realtime CRUD Web App Quickly (/post/5e50b735525fc968b04a707f/mean-stack-
angular-9-build-realtime-crud-web-app-quickly)
Build a web app using Nestjs, Fastify, MongoDB and Angular 8 (/post/5d2898430707cc5968d9d57f/build-a-web-app-
using-nestjs-fastify-mongodb-and-angular-8)
Node, Express, PostgreSQL, Vue 2 and GraphQL CRUD Web App (/post/5cdc0ba280aca754f7a9d1f4/node-express-
postgresql-vue-2-and-graphql-crud-web-app)
Node, Express, React.js, Graphql and MongoDB CRUD Web Application (/post/5cbd1e9a80aca754f7a9d1f2/node-
express-reactjs-graphql-and-mongodb-crud-web-application)
Node.js, Express.js and Multer Restful API for Image Uploader (/post/5c98220080aca754f7a9d1f0/nodejs-expressjs-
and-multer-restful-api-for-image-uploader)
Node, Express, Angular 7, GraphQL and MongoDB CRUD Web App (/post/5c75d68880aca754f7a9d1ed/node-express-
angular-7-graphql-and-mongodb-crud-web-app)
Secure Node.js, Express.js and PostgreSQL API using Passport.js (/post/5bf94d9a80aca747f4b9ce9f/secure-nodejs-
expressjs-and-postgresql-api-using-passportjs)
Node, Express, Sequelize, and PostgreSQL Association Example (/post/5bb1f05280aca74669894417/node-express-
sequelize-and-postgresql-association-example)
MongoDB, Express, Vue.js 2, Node.js (MEVN) and SocketIO Chat App (/post/5b6a681f80aca76a2cbd98fb/mongodb-
express-vuejs-2-nodejs-mevn-and-socketio-chat-app)
Node.js, Express.js, Sequelize.js and PostgreSQL RESTful API (/post/5b56a6cc80aca707dd4f65a9/nodejs-expressjs-
sequelizejs-and-postgresql-restful-api)
MEAN Stack Angular 6 CRUD Web Application (/post/5b00bb9180aca726dee1fd6d/mean-stack-angular-6-crud-web-
application)
Securing MEVN Stack (Vue.js 2) Web Application using Passport (/post/5ac8338780aca714d19d5b9e/securing-mevn-
stack-vuejs-2-web-application-using-passport)
Securing MERN Stack Web Application using Passport (/post/5a90c37980aca7059c14297a/securing-mern-stack-web-
application-using-passport)
Securing MEAN Stack (Angular 5) Web Application using Passport (/post/5a878b3c80aca7059c142979/securing-
mean-stack-angular-5-web-application-using-passport)
Setup Node.js, Nginx and MongoDB on Ubuntu 16.04 for Production (/post/5a593bfc80aca7059c142975/setup-nodejs-
nginx-and-mongodb-on-ubuntu-1604-for-production)
Node.js and MongoDB Slack Bot Example (/post/5a500c9380aca7059c142973/nodejs-and-mongodb-slack-bot-
example)
Mongo Express Vue Node.js (MEVN Stack) CRUD Web Application (/post/5a1b779f80aca75eadc12d6e/mongo-
express-vue-nodejs-mevn-stack-crud-web-application)
MEAN Stack (Angular 5) CRUD Web Application Example (/post/5a0673c880aca7739224ee21/mean-stack-angular-5-
crud-web-application-example)
Building CRUD Web Application using MERN Stack (/post/59faec0a80aca7739224ee1f/building-crud-web-application-
using-mern-stack)
Node Express Passport Facebook Twitter Google GitHub Login (/post/59a6257180aca768e4d2b132/node-express-
passport-facebook-twitter-google-github-login)

7 Comments Djamware - Fullstack Programming Tutorials 🔒 Disqus' Privacy Policy 1 Login

 Recommend t Tweet f Share Sort by Best

Join the discussion…

LOG IN WITH
OR SIGN UP WITH DISQUS ?

Name

Summer Basehart • a month ago

Why does the loggedinstatus change after page refresh?

1 △ ▽ • Reply • Share ›

Asfath Ahamed • 2 months ago

login router and register router reversed

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 45/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS

1 △ ▽ • Reply • Share ›

Gianluca Lella • 4 months ago

Why do I have this error?

./node_modules/mat-contenteditable/fesm2015/mat-contenteditable.js

Module not found: Error: Can't resolve '@ckeditor/ckeditor5-angular'

△ ▽ • Reply • Share ›

Gianluca Lella > Gianluca Lella • 4 months ago

ok it was a problem related to the version! :)

△ ▽ • Reply • Share ›

Flex Kart • 4 months ago

how to display title in url like wordpress slug instead of id

△ ▽ • Reply • Share ›

Charanjit Singh • 4 months ago • edited

Getting some error in runing this kindly help

firstly when running nodemon command after completing everything

internal/modules/cjs/loader.js:797

throw err;

Error: Cannot find module '../config/database'

Require stack:

- C:\blog-cms\routes\auth.js

- C:\blog-cms\app.js

- C:\blog-cms\bin\www

at Function.Module._resolveFilename (internal/modules/cjs/loader.js:794:15)

at Function.Module._load (internal/modules/cjs/loader.js:687:27)

at Module.require (internal/modules/cjs/loader.js:849:19)

at require (internal/modules/cjs/helpers.js:74:18)

at Object.<anonymous> (C:\blog-cms\routes\auth.js:3:14)

at Module._compile (internal/modules/cjs/loader.js:956:30)

at Object.Module._extensions..js (internal/modules/cjs/loader.js:973:10)

at Module.load (internal/modules/cjs/loader.js:812:32)

at Function.Module._load (internal/modules/cjs/loader.js:724:14)

at Module.require (internal/modules/cjs/loader.js:849:19)

at require (internal/modules/cjs/helpers.js:74:18)

at Object.<anonymous> (C:\blog-cms\app.js:6:12)

at Module._compile (internal/modules/cjs/loader.js:956:30)

at Function.Module._load (internal/modules/cjs/loader.js:724:1js:973:10)14) {

code: 'MODULE_NOT_FOUND', 4) {

requireStack: [

'C:\\blog-cms\\routes\\auth.js',

'C:\\blog-cms\\app.js',

'C:\\blog-cms\\bin\\www'

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 46/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
}

[nodemon] app crashed - waiting for file changes before starting....

After running ng serve command i got this error.

@Output() isLoggedIn: EventEmitter<any> = new EventEmitter();

src/app/auth.service.ts:11:41 - error TS1109: Expression expected.

11 @Output() isLoggedIn: EventEmitter<any> = new EventEmitter();

** Angular Live Development Server is listening on localhost:4200, open your browser on http:/, open your browser on

http://localhost:4200/ **

i 「wdm」: Failed to compile.

△ ▽ • Reply • Share ›

faizplus > Charanjit Singh • 4 months ago

change ../config/database to ../config/settings

1 △ ▽ • Reply • Share ›

✉ Subscribe d Add Disqus to your siteAdd DisqusAdd ⚠ Do Not Sell My Data

Programming Blog
(/post-category/595f867edbd39e7571e183dc/programming-blog)
Ionic Framework
(/post-sub-category/5845691a80aca7763489d872/ionic-framework)
MongoDB
(/post-sub-category/5845677b80aca7763489d871/mongodb)
React Native
(/post-sub-category/5b4aa0b480aca707dd4f65a6/react-native)
Groovy and Grails
(/post-sub-category/585b3fa380aca73b19a2efd4/groovy-and-grails)
ASP.NET Core
(/post-sub-category/5c50643780aca754f7a9d1e9/aspnet-core)
HTML 5 Tutorial
(/post-sub-category/584209dffcbe618f680bdc5c/html-5-tutorial)
Java
(/post-sub-category/583d6c37fcbe614473a4c4e8/java)
CSS 3
(/post-sub-category/584249bde4d5d303658d1ecf/css-3)
Javascript
(/post-sub-category/583d6d30fcbe614473a4c4e9/javascript)
Flutter Tutorial
(/post-sub-category/5e1dce2a11c01b0ae0c1c7d9/flutter-tutorial)
Node.js
(/post-sub-category/58a9196f80aca748640ce352/nodejs)
Angular
(/post-sub-category/5d5cb64ffdef3404227777c3/angular)
React.js
(/post-sub-category/5d5cf43bfdef3404227777c5/reactjs)
Vue.js
(/post-sub-category/5d5cf310fdef3404227777c4/vuejs)
All Articles
(/public/allArticles)

Popular Articles:
Angular 8 Tutorial: REST API and HttpClient Examples (51131)
(/post/5d8d7fc10daa6c77eed3b2f2/angular-8-tutorial-rest-api-and-httpclient-examples)
Angular 8 Tutorial: Routing & Navigation Example (28090)
(/post/5d58b409bcc156d4a8a3df8f/angular-8-tutorial-routing-navigation-example)

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 47/48
11/03/2020 MEAN Stack (Angular 8) Tutorial: Build a Simple Blog CMS
Angular 8 Tutorial: Observable and RXJS Examples (21643)
(/post/5da31946ae418d042e1aef1d/angular-8-tutorial-observable-and-rxjs-examples)
Angular 6 HttpClient: Consume RESTful API Example (15478)
(/post/5b87894280aca74669894414/angular-6-httpclient-consume-restful-api-example)
Angular 7 Tutorial: Building CRUD Web Application (13927)
(/post/5bca67d780aca7466989441f/angular-7-tutorial-building-crud-web-application)
Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly (9716)
(/post/5e435e84a8d0ef4300ffc5f6/angular-9-tutorial-learn-to-build-a-crud-angular-app-quickly)
Angular 8 Tutorial: Learn to Build Angular 8 CRUD Web App (9149)
(/post/5d0eda6f80aca754f7a9d1f5/angular-8-tutorial-learn-to-build-angular-8-crud-web-app)
Angular 8 Tutorial: How to Create an Angular Web App Quickly (8197)
(/post/5dde48a430ca212bd263b1b6/angular-8-tutorial-how-to-create-an-angular-web-app-quickly)
Angular Material Form Controls Select (mat-select) Example (7466)
(/post/5e379e4fd598ef0e8bbcc67e/angular-material-form-controls-select-mat-select-example)
Angular Material Form Controls, Form Field and Input Examples (6869)
(/post/5db6c743e3ba1827589f6856/angular-material-form-controls-form-field-and-input-examples)
React Native Tutorial: SQLite Offline Android/iOS Mobile App (6573)
(/post/5caec76380aca754f7a9d1f1/react-native-tutorial-sqlite-offline-androidios-mobile-app)
Push Notification using Ionic 4 and Firebase Cloud Messaging (6505)
(/post/5c6ccd1f80aca754f7a9d1ec/push-notification-using-ionic-4-and-firebase-cloud-messaging)
Ionic 4 Angular 8 Tutorial: Learn to Build CRUD Mobile Apps (5702)
(/post/5d12b5b880aca754f7a9d1f6/ionic-4-angular-8-tutorial-learn-to-build-crud-mobile-apps)
React Native Firebase Cloud Messaging (FCM) Push Notification (5530)
(/post/5d7773f05d8cdc057b603e65/react-native-firebase-cloud-messaging-fcm-push-notification)
Ionic 4, Angular 7 and Cordova Tutorial: Build CRUD Mobile Apps (5398)
(/post/5be52ce280aca72b942e31bc/ionic-4-angular-7-and-cordova-tutorial-build-crud-mobile-apps)

©2012-2018 Djamware.com (http://Djamware.com) | Privacy Policy (/public/privacy) | About Us (/public/about) | Contact Us (/public/contact) | RSS
(https://www.djamware.com/feeds)

https://www.djamware.com/post/5d88cb43e7939eec17dc4c89/mean-stack-angular-8-tutorial-build-a-simple-blog-cms 48/48

You might also like