Originally published by Didin J. on at djamware.com
A comprehensive step by step tutorial to building a full CRUD Angular 8 Universal and MongoDB Server-side Rendering (SSR). For more detailed information about Angular 8 Universal and SSR, you can find in the official Angular blog. Here. we will practically use Angular 8 universal for a CRUD (Create, Read, Update, Delete) operation application that store the data to the MongoDB server.
The Angular 8 application that we will build is a very simple application about Article CRUD. This might be a starting point to build your own complete blog engine or news engine because this application should be matched for SEO requirements.
Almost all codes in this application using Typescript and ES6 except a few files that still use plain ES5 code that uses by Express.js. For that, we need your suggestion for converting to working Angular 8/Typescript code styles. The following tools, frameworks, and modules are required for this tutorial:
As usual, before we move forward to the main steps of Angular 8 Universal and MongoDB Server-side Rendering (SSR). We have to check the latest Node and Angular versions then update them if necessary. Just type this command to see it.
ng version
You will see the currently installed Node, Angular and Angular CLI versions.
Angular CLI: 8.0.6 Node: 10.15.1 OS: darwin x64 Angular: 8.0.3
First, we will create a new Angular 8 application by type this command in the terminal.
ng new article-crud
Next, go to the newly created Angular 8 application folder then type this command to add Angular Universal SSR.
ng add @nguniversal/express-engine --clientProject article-crud
That command just added and updated a few files.
CREATE src/main.server.ts (361 bytes) CREATE src/app/app.server.module.ts (427 bytes) CREATE tsconfig.server.json (204 bytes) CREATE webpack.server.config.js (1466 bytes) CREATE server.ts (1980 bytes) UPDATE package.json (1844 bytes) UPDATE angular.json (4263 bytes) UPDATE src/main.ts (432 bytes) UPDATE src/app/app.module.ts (438 bytes)
Next, check the Angular Universal SSR application by running this application using these commands.
npm run build:ssr && npm run serve:ssr
Now, after go to `localhost:4000` you see this standard Angular application page and feel the difference with the page that rendered in the browser.
To store and retrieve data from the MongoDB server, we will use Mongoose.js. To install it, just run these commands that including `body-parser` modules.
npm install --save mongoose body-parser
In this Angular 8 Universal SSR, body-parser will use to parse the request body to the API. Next, open and edit `server.ts` in the root project folder then add these imports.
import * as mongoose from 'mongoose'; import bodyParser from "body-parser";
Next, add these lines of codes after the imports lines to connect to the MongoDB and use body-parser for every JSON requests.
mongoose.connect('mongodb://localhost/angular8-crud', { useNewUrlParser: true, useFindAndModify: false }) .then(() => console.log('connection successful')) .catch((err) => console.error(err));app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
We will create a Mongoose model that represent MongoDB fields. Create a models folder and a file to holds all required Article fields.
mkdir models
touch models/article.ts
Next, fill that file with this Mongoose Schema.
import mongoose, { Schema } from ‘mongoose’;const ArticleSchema: Schema = new Schema({
title: { type: String, required: true },
author: { type: String, required: true },
description: { type: String, required: true },
content: { type: String, required: true },
updatedAt: { type: Date, default: Date.now }
});export default mongoose.model(‘Article’, ArticleSchema);
Next, we will create a route that accesses the MongoDB data via API. Create a new folder call routes
and a file inside it.
mkdir routes
touch routes/article-route.ts
Open and edit that file then add these imports.
import { Request, Response, NextFunction } from ‘express’;
import Article from ‘…/models/article’;
Add these lines of codes to create a CRUD method of RESTful API. The route path starts with /api/
that will be called from the Angular 8 application using full URL <a href="http://localhost:4000/api/" target="_blank">http://localhost:4000/api/</a>
.
export class ArticleRoute {public articleRoute(app): void {
app.route(‘/api/’).get((req: Request, res: Response, next: NextFunction) => {
Article.find((err, articles) => {
if (err) { return next(err); }
res.json(articles);
});
});app.route(‘/api/:id’).get((req: Request, res: Response, next: NextFunction) => {
Article.findById(req.params.id, (err, article) => {
if (err) { return next(err); }
res.json(article);
});
});app.route(‘/api/’).post((req: Request, res: Response, next: NextFunction) => {
console.log(req.body);
Article.create(req.body, (err, article) => {
if (err) { return next(err); }
res.json(article);
});
});app.route(‘/api/:id’).put((req: Request, res: Response, next: NextFunction) => {
Article.findByIdAndUpdate(req.params.id, req.body, (err, article) => {
if (err) { return next(err); }
res.json(article);
});
});app.route(‘/api/:id’).delete((req: Request, res: Response, next: NextFunction) => {
Article.findByIdAndRemove(req.params.id, req.body, (err, article) => {
if (err) { return next(err); }
res.json(article);
});
});
}
}
Next, open and edit server.ts
then add this import.
import { ArticleRoute } from ‘./routes/article-route’;
Declare a constant after the import lines.
const articleRoute: ArticleRoute = new ArticleRoute();
Add this line before the all regular route
lines.
articleRoute.articleRoute(app);
We will test the Angular 8 Universal SSR RESTful API using the Postman app. First, run the app using this command.
npm run build:ssr && npm run serve:ssr
Open the postman app then change the method to GET and address to localhost:4000/api/
then press SEND
button. If there’s any data in your MongoDB server then it’s will be similar like this.
To save data, change the method to POST with the same address then choose RAW body with type JSON. Fill the RAW body with this example or your own data example.
{
“title”: “Odong”,
“author”: “Odong”,
“description”: “Odong”,
“content”: “Odong”
}
You will see this result for successful POST data.
Next, you can test the rest of PUT and DELETE using your own data to make sure the Angular 8 Universal SSR API working.
To create Angular 8 Routes for navigation between Angular 8 pages/component, add or generate all required component.
ng g component articles --skip-import
ng g component show-article --skip-import
ng g component add-article --skip-import
ng g component edit-article --skip-import
There’s an additional --skip-import
variable because there will be a conflict between 2 modules, Angular 8 and Server modules.
More than one module matches. Use skip-import option to skip importing the component into the closest module.
Next, we will manually add or register those components to the src/app/app.module.ts
. Add these imports to that file.
import { ArticlesComponent } from ‘./articles/articles.component’;
import { ShowArticleComponent } from ‘./show-article/show-article.component’;
import { AddArticleComponent } from ‘./add-article/add-article.component’;
import { EditArticleComponent } from ‘./edit-article/edit-article.component’;
Then add those components to @NgModule
declaration.
@NgModule({
declarations: [
…
ArticlesComponent,
ShowArticleComponent,
AddArticleComponent,
EditArticleComponent
],
…
})
Next, open and edit src/app/app-routing.module.ts
then add these imports.
const routes: Routes = [
{
path: ‘articles’,
component: ArticlesComponent,
data: { title: ‘Articles’ }
},
{
path: ‘show-article/:id’,
component: ShowArticleComponent,
data: { title: ‘Show Article’ }
},
{
path: ‘add-article’,
component: AddArticleComponent,
data: { title: ‘Add Article’ }
},
{
path: ‘edit-article/:id’,
component: EditArticleComponent,
data: { title: ‘Edit Article’ }
},
{ path: ‘’,
redirectTo: ‘/articles’,
pathMatch: ‘full’
}
];
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.
<div class=“container”>
<router-outlet></router-outlet>
</div>
Open and edit src/app/app.component.scss
then replace all SASS codes with this.
.container {
padding: 20px;
}
To access RESTful API from Angular 8, we need to create an Angular 8 service which will handle all POST, GET, UPDATE, DELETE requests. The response from the RESTful API emitted by Observable that can subscribe and read from the Components. For error handler and data Extraction, we will use RXJS Library. Before creating a service for RESTful API access, first, we have to install or register HttpClientModule
. Open and edit src/app/app.module.ts
then add this import.
import { FormsModule } from ‘@angular/forms’;
import { HttpClientModule } from ‘@angular/common/http’;
Add it to @NgModule
imports after BrowserModule
.
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
AppRoutingModule
],
We will use type specifier to get a typed result object. For that, create a new Typescript file src/app/article.ts
then add these lines of Typescript codes.
export class Article {
_id: string;
title: string;
author: string;
description: string;
content: string;
updatedAt: Date;
}
Next, generate an Angular 8 service by typing this command.
ng g service api
Next, open and edit src/app/api.service.ts
then add these imports.
import { Observable, of, throwError } from ‘rxjs’;
import { HttpClient, HttpHeaders, HttpErrorResponse } from ‘@angular/common/http’;
import { catchError, tap, map } from ‘rxjs/operators’;
import { Article } from ‘./article’;
Add these constants before the @Injectable
.
const httpOptions = {
headers: new HttpHeaders({‘Content-Type’: ‘application/json’})
};
const apiUrl = “/api/”;
Inject HttpClient
module to the constructor.
constructor(private http: HttpClient) { }
Add the error handler function.
private handleError<T>(operation = ‘operation’, result?: T) {
return (error: any): Observable<T> => {
console.error(error); // log to console instead
return of(result as T);
};
}
Add the functions for all CRUD (create, read, update, delete) RESTful call of article data.
getArticles(): Observable<Article[]> {
return this.http.get<Article[]>(apiUrl)
.pipe(
tap(articles => console.log(‘fetched Articles’)),
catchError(this.handleError(‘getArticles’, []))
);
}getArticle(id: number): Observable<Article> {
const url =${apiUrl}/${id}
;
return this.http.get<Article>(url).pipe(
tap(_ => console.log(fetched Article id=${id}
)),
catchError(this.handleError<Article>(getArticle id=${id}
))
);
}addArticle(article: Article): Observable<Article> {
return this.http.post<Article>(apiUrl, article, httpOptions).pipe(
tap((art: Article) => console.log(added Article w/ id=${art._id}
)),
catchError(this.handleError<Article>(‘addArticle’))
);
}updateArticle(id: any, article: Article): Observable<any> {
const url =${apiUrl}/${id}
;
return this.http.put(url, article, httpOptions).pipe(
tap(_ => console.log(updated Article id=${id}
)),
catchError(this.handleError<any>(‘updateArticle’))
);
}deleteArticle(id: any): Observable<Article> {
const url =${apiUrl}/${id}
;
return this.http.delete<Article>(url, httpOptions).pipe(
tap(_ => console.log(deleted Article id=${id}
)),
catchError(this.handleError<Article>(‘deleteArticle’))
);
}
You can find more details about Angular 8 Observable and RXJS here.
We will display the list of articles published from API Service. The data published from the API service read by subscribing as an Article model in the Angular 8 component. For that, open and edit src/app/articles/articles.component.ts
then add these imports.
import { ApiService } from ‘…/api.service’;
Next, inject the API Service to the constructor.
constructor(private api: ApiService) { }
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.
ng add @angular/material
If there are questions like below, just use the default answer.
? Choose a prebuilt theme name, or “custom” for a custom theme: Purple/Green [ Preview: h
ttps://material.angular.io?theme=purple-green ]
? 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.
import {
MatInputModule,
MatPaginatorModule,
MatProgressSpinnerModule,
MatSortModule,
MatTableModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule } 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,
FormsModule,
HttpClientModule,
AppRoutingModule,
ReactiveFormsModule,
BrowserAnimationsModule,
MatInputModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatProgressSpinnerModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule
],
Next, back to src/app/articles/articles.component.ts
then add these imports.
import { Article } from ‘…/article’;
Declare the variables of Angular 8 Material Table Data Source before the constructor.
displayedColumns: string[] = [‘title’, ‘author’];
data: Article[] = [];
isLoadingResults = true;
Modify the ngOnInit
function to get list of articles immediately.
ngOnInit() {
this.api.getArticles()
.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/articles/articles.component.html
then replace all HTML tags with this Angular Material tags.
<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-article’]”><mat-icon>add</mat-icon></a>
</div>
<div class=“mat-elevation-z8”>
<table mat-table [dataSource]=“data” class=“example-table”
matSort matSortActive=“title” matSortDisableClear matSortDirection=“asc”><!-- Article Title Column -->
<ng-container matColumnDef=“title”>
<th mat-header-cell *matHeaderCellDef>Title</th>
<td mat-cell *matCellDef=“let row”>{{row.title}}</td>
</ng-container><!-- Article Author Column -->
<ng-container matColumnDef=“author”>
<th mat-header-cell *matHeaderCellDef>Author</th>
<td mat-cell *matCellDef=“let row”>$ {{row.author}}</td>
</ng-container><tr mat-header-row *matHeaderRowDef=“displayedColumns”></tr>
<tr mat-row matRowDef=“let row; columns: displayedColumns;” [routerLink]=“[‘/show-article/’, row._id]”></tr>
</table>
</div>
</div>
Finally, to make a little UI adjustment, open and edit src/app/articles/articles.component.scss
then add this CSS codes.
/ Structure */
.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;
}
To show article details after click or tap on the one of a row inside the Angular 8 Material table, open and edit src/app/show-article/show-article.component.ts
then add these imports.
import { ActivatedRoute, Router } from ‘@angular/router’;
import { ApiService } from ‘…/api.service’;
import { Article } from ‘…/article’;
Inject above modules to the constructor.
constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }
Declare the variables before the constructor for hold article data that get from the API.
article: Article = { _id: ‘’, title: ‘’, author: ‘’, description: ‘’, content: ‘’, updatedAt: null };
isLoadingResults = true;
Add a function for getting Article data from the API.
getArticleDetails(id: any) {
this.api.getArticle(id)
.subscribe((data: any) => {
this.article = data;
console.log(this.article);
this.isLoadingResults = false;
});
}
Call that function when the component is initiated.
ngOnInit() {
this.getArticleDetails(this.route.snapshot.params.id);
}
Add this function for delete article.
deleteArticle(id: any) {
this.isLoadingResults = true;
this.api.deleteArticle(id)
.subscribe(res => {
this.isLoadingResults = false;
this.router.navigate([‘/articles’]);
}, (err) => {
console.log(err);
this.isLoadingResults = false;
}
);
}
For the view, open and edit src/app/article-detail/article-detail.component.html
then replace all HTML tags with this.
<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]=“[‘/articles’]”><mat-icon>list</mat-icon></a>
</div>
<mat-card class=“example-card”>
<mat-card-header>
<mat-card-title><h2>{{article.title}}</h2></mat-card-title>
<mat-card-subtitle>{{article.author}} | {{article.updatedAt}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<h3>{{article.description}}</h3>
<p>{{article.content}}</p>
</mat-card-content>
<mat-card-actions>
<a mat-flat-button color=“primary” [routerLink]=“[‘/edit-article’, article._id]”><mat-icon>edit</mat-icon></a>
<a mat-flat-button color=“warn” (click)=“deleteArticle(article._id)”><mat-icon>delete</mat-icon></a>
</mat-card-actions>
</mat-card>
</div>
Finally, open and edit src/app/article-detail/article-detail.component.scss
then add this lines of CSS codes.
/ Structure */
.example-container {
position: relative;
padding: 5px;
}.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;
}.mat-flat-button {
margin: 5px;
}
To create a form for adding a Article, open and edit src/app/add-article/add-article.component.ts
then add these imports.
import { Router } from ‘@angular/router’;
import { ApiService } from ‘…/api.service’;
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from ‘@angular/forms’;
Inject above modules to the constructor.
constructor(private router: Router, private api: ApiService, private formBuilder: FormBuilder) { }
Declare variables for the Form Group and all of the required fields inside the form before the constructor.
articleForm: FormGroup;
title = ‘’;
author = ‘’;
description = ‘’;
content = ‘’;
isLoadingResults = false;
Add initial validation for each field.
ngOnInit() {
this.articleForm = this.formBuilder.group({
‘title’ : [null, Validators.required],
‘author’ : [null, Validators.required],
‘description’ : [null, Validators.required],
‘content’ : [null, Validators.required]
});
}
Create a function for submitting or POST article form.
onFormSubmit() {
this.isLoadingResults = true;
this.api.addArticle(this.articleForm.value)
.subscribe((res: any) => {
const id = res._id;
this.isLoadingResults = false;
this.router.navigate([‘/show-article’, id]);
}, (err: any) => {
console.log(err);
this.isLoadingResults = false;
});
}
Next, add this import for implementing ErrorStateMatcher
.
import { ErrorStateMatcher } from ‘@angular/material/core’;
Create a new class before the main class @Components
.
/** 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));
}
}
Instantiate that MyErrorStateMatcher
as a variable in main class.
matcher = new MyErrorStateMatcher();
Next, open and edit src/app/add-article/add-article.component.html
then replace all HTML tags with this.
<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]=“[‘/articles’]”><mat-icon>list</mat-icon></a>
</div>
<mat-card class=“example-card”>
<form [formGroup]=“articleForm” (ngSubmit)=“onFormSubmit()”>
<mat-form-field class=“example-full-width”>
<input matInput placeholder=“Title” formControlName=“title”
[errorStateMatcher]=“matcher”>
<mat-error>
<span *ngIf=“!articleForm.get(‘title’).valid && articleForm.get(‘title’).touched”>Please enter Title</span>
</mat-error>
</mat-form-field>
<mat-form-field class=“example-full-width”>
<input matInput placeholder=“Author” formControlName=“author”
[errorStateMatcher]=“matcher”>
<mat-error>
<span *ngIf=“!articleForm.get(‘author’).valid && articleForm.get(‘author’).touched”>Please enter Author</span>
</mat-error>
</mat-form-field>
<mat-form-field class=“example-full-width”>
<input matInput placeholder=“Description” formControlName=“description”
[errorStateMatcher]=“matcher”>
<mat-error>
<span *ngIf=“!articleForm.get(‘description’).valid && articleForm.get(‘description’).touched”>Please enter Description</span>
</mat-error>
</mat-form-field>
<mat-form-field class=“example-full-width”>
<textarea matInput placeholder=“Content” formControlName=“content”
[errorStateMatcher]=“matcher”></textarea>
<mat-error>
<span ngIf=“!articleForm.get(‘content’).valid && articleForm.get(‘content’).touched”>Please enter Content</span>
</mat-error>
</mat-form-field>
<div class=“button-row”>
<button type=“submit” [disabled]=“!articleForm.valid” mat-flat-button color=“primary”><mat-icon>save</mat-icon></button>
</div>
</form>
</mat-card>
</div>
Finally, open and edit src/app/article-add/article-add.component.scss
then add this CSS codes.
/ Structure */
.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;
}
We have put an edit button inside the Article Detail component for call Edit page. Now, open and edit src/app/edit-article/edit-article.component.ts
then add these imports.
import { Router, ActivatedRoute } from ‘@angular/router’;
import { ApiService } from ‘…/api.service’;
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from ‘@angular/forms’;
Inject above modules to the constructor.
constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }
Declare the Form Group variable and all of the required variables for the article form before the constructor.
articleForm: FormGroup;
_id = ‘’;
title = ‘’;
author = ‘’;
description = ‘’;
content = ‘’;
isLoadingResults = false;
Next, add validation for all fields when the component is initiated.
ngOnInit() {
this.getArticle(this.route.snapshot.params.id);
this.articleForm = this.formBuilder.group({
‘title’ : [null, Validators.required],
‘author’ : [null, Validators.required],
‘description’ : [null, Validators.required],
‘content’ : [null, Validators.required]
});
}
Create a function for getting article data that filled to each form fields.
getArticle(id: any) {
this.api.getArticle(id).subscribe((data: any) => {
this._id = data._id;
this.articleForm.setValue({
title: data.title,
author: data.author,
description: data.description,
content: data.content
});
});
}
Create a function to update the article changes.
onFormSubmit() {
this.isLoadingResults = true;
this.api.updateArticle(this._id, this.articleForm.value)
.subscribe((res: any) => {
const id = res._id;
this.isLoadingResults = false;
this.router.navigate([‘/show-article’, id]);
}, (err: any) => {
console.log(err);
this.isLoadingResults = false;
}
);
}
Add a function for handling the show article details button.
articleDetails() {
this.router.navigate([‘/show-article’, this._id]);
}
Next, add this import for implementing ErrorStateMatcher
.
import { ErrorStateMatcher } from ‘@angular/material/core’;
Create a new class before the main class @Components
.
/** 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));
}
}
Instantiate that MyErrorStateMatcher
as a variable in main class.
matcher = new MyErrorStateMatcher();
Next, open and edit src/app/edit-article/edit-article.component.html
then replace all HTML tags with this.
<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)=“articleDetails()”><mat-icon>info</mat-icon></a>
</div>
<mat-card class=“example-card”>
<form [formGroup]=“articleForm” (ngSubmit)=“onFormSubmit()”>
<mat-form-field class=“example-full-width”>
<input matInput placeholder=“Title” formControlName=“title”
[errorStateMatcher]=“matcher”>
<mat-error>
<span *ngIf=“!articleForm.get(‘title’).valid && articleForm.get(‘title’).touched”>Please enter Title</span>
</mat-error>
</mat-form-field>
<mat-form-field class=“example-full-width”>
<input matInput placeholder=“Author” formControlName=“author”
[errorStateMatcher]=“matcher”>
<mat-error>
<span *ngIf=“!articleForm.get(‘author’).valid && articleForm.get(‘author’).touched”>Please enter Author</span>
</mat-error>
</mat-form-field>
<mat-form-field class=“example-full-width”>
<input matInput placeholder=“Description” formControlName=“description”
[errorStateMatcher]=“matcher”>
<mat-error>
<span *ngIf=“!articleForm.get(‘description’).valid && articleForm.get(‘description’).touched”>Please enter Description</span>
</mat-error>
</mat-form-field>
<mat-form-field class=“example-full-width”>
<textarea matInput placeholder=“Content” formControlName=“content”
[errorStateMatcher]=“matcher”></textarea>
<mat-error>
<span ngIf=“!articleForm.get(‘content’).valid && articleForm.get(‘content’).touched”>Please enter Content</span>
</mat-error>
</mat-form-field>
<div class=“button-row”>
<button type=“submit” [disabled]=“!articleForm.valid” mat-flat-button color=“primary”><mat-icon>save</mat-icon></button>
</div>
</form>
</mat-card>
</div>
Finally, open and edit src/app/edit-article/edit-article.component.scss
then add this lines of CSS codes.
/ Structure */
.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;
}
It’s time to test and try the performance of Angular 8 Universal and MongoDB Server-side Rendering (SSR). First, run the MongoDB server in the different terminal or command line.
mongod
Next, run the Angular 8 Universal SSR in the current terminal and project folder.
npm run build:ssr && npm run serve:ssr
After build finished for both client and server then open the application in the browser <a href="http://localhost:4200/" target="_blank">http://localhost:4200/</a>
and you will see and feel this Angular 8 application as below.
That it’s, the example of Angular 8 Universal and MongoDB Server-side Rendering (SSR). You can get the full source codes from our Github.
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. Choose your template for your front-end project here.
That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can take the following cheap course:
Originally published by Didin J. on at djamware.com
====================================================
Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter
#angular #mongodb #web-development