Professional Application Development in MEAN Stack - Part 9 - C
Date Published: 11/05/2018
In the last part of Part - 9, we will update the View Page to render the dynamically created page through Manage Page component in the Admin section. Our top menu is also managed in the Admin section, the Manage Page section stores the corresponding menu item when we create the new page. When the user will click on any menu item, the menu ID will pass to View Page that will load the page HTML from the database and render it on the screen.

Introduction

This is the last part of Part 9 where finally we will render the dynamically created page. The menu and page are connected to each other through menu ID. When a user clicks on any menu item, menu ID is being passed to View Page. We will remove all the static HTML and related typescript in ViewPage component, write new code to take the menu ID and load the corresponding page's HTML from the database. We will also introduce a new Pipe to properly render the HTML.

Let's Start

It is strongly recommended to read all previous parts before moving to this one, can't promise you would understand all the steps I am going to list down without knowledge of previous parts.

  1. As always, make sure the last part is working fine, you are successfully able to Add, Update and Delete the page and its content.
  2. First, let's create a custom Pipe to bypass any kind of HTML special characters e.g. any type of scripting (Javascript, C#, Java etc.) to properly view the code on screen, this is an optional step, if you know that it is not required for your application, its better NOT to use it cause it makes your application vulnerable to Cross Site Scripting (XSS) where user can inject malicious Javascript in your code and screw your application. I am only using it here to show you how you can actually use the DomSanitizer class. Read more about DomSanitizer from here.
  3. Right click on src -> app -> shared folder, select the option, New File. Enter the name keepHtml.pipe.ts. Paste the following code in it:  
    import { Pipe, PipeTransform } from '@angular/core';
    import { DomSanitizer } from '@angular/platform-browser';
    
    @Pipe({ name: 'keepHtml', pure: false })
    export class EscapeHtmlPipe implements PipeTransform {
      constructor(private sanitizer: DomSanitizer) {
      }
    
      transform(content) {
        return this.sanitizer.bypassSecurityTrustHtml(content);
      }
    }
  4. The class metadata defines that it is a Pipe with the name keepHtml. We are implementing a PipeTransform interface that has only one method transform taking the input value that in our case would be the page HTML. The bypassSecurityTrustHtml() method will let all HTML render as it is without sanitizing the potential dangerous text e.g. <script>some crapy script</script> that otherwise would be viewed as &lt;script&gt;some crapy script&lt;/script&gt; where < and > are converted into &lt and &gt to avoid executing the unwanted script.
  5. Next, add the reference of keepHtml Pipe in AppModule, edit the src -> app -> app.module.ts and replace its content with the following:   
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    //Http Module For GET, POST, PUT and DELETE RESTful APIs
    import { HttpModule } from '@angular/http';
    import { TinymceModule } from 'angular2-tinymce';
    //keepHtml Reference
    import { EscapeHtmlPipe } from "./shared/keepHtml.pipe";
    import {
      MatAutocompleteModule,
      MatButtonModule,
      MatButtonToggleModule,
      MatCardModule,
      MatCheckboxModule,
      MatChipsModule,
      MatDatepickerModule,
      MatDialogModule,
      MatExpansionModule,
      MatGridListModule,
      MatIconModule,
      MatInputModule,
      MatListModule,
      MatMenuModule,
      MatNativeDateModule,
      MatPaginatorModule,
      MatProgressBarModule,
      MatProgressSpinnerModule,
      MatRadioModule,
      MatRippleModule,
      MatSelectModule,
      MatSidenavModule,
      MatSliderModule,
      MatSlideToggleModule,
      MatSnackBarModule,
      MatSortModule,
      MatTableModule,
      MatTabsModule,
      MatToolbarModule,
      MatTooltipModule,
      MatStepperModule
    } from '@angular/material';
    
    import { HttpClientModule } from '@angular/common/http';
    
    import { AppComponent } from './app.component';
    import 'hammerjs';
    
    import { AgmCoreModule } from '@agm/core';
    import { NgxCarouselModule } from 'ngx-carousel';
    
    import { HomeComponent } from './client/home/home.component';
    import { AppRoutingModule } from "./app-routing.module";
    import { ContactComponent } from './client/contact/contact.component';
    import { DataService } from './service/data/data.service';
    import { Util } from "./shared/util";
    import { ViewPageComponent } from './client/view-page/view-page.component';
    import { FooterComponent } from './footer.component';
    import { AdminComponent } from './admin/admin/admin.component';
    import { DatagridComponent } from './shared/datagrid/datagrid.component';
    import { ConfirmDeleteComponent } from './shared/messages/confirm-delete/confirm-delete.component';
    import { ManageMenuComponent } from './admin/menu/manage-menu/manage-menu.component';
    import { MenuListComponent } from './admin/menu/menu-list/menu-list.component';
    import { UserMessageComponent } from './admin/user-message/user-message.component';
    import { PageListComponent } from './admin/page/page-list/page-list.component';
    import { ManagePageComponent } from './admin/page/manage-page/manage-page.component';
    
    @NgModule({
      declarations: [
        AppComponent,
        HomeComponent,
        ContactComponent,
        ViewPageComponent,
        FooterComponent,
        AdminComponent,
        DatagridComponent,
        ConfirmDeleteComponent,
        ManageMenuComponent,
        MenuListComponent,
        UserMessageComponent,
        PageListComponent,
        ManagePageComponent,
        EscapeHtmlPipe
      ],
      imports: [
        TinymceModule.withConfig({
          allow_script_urls: true,
          convert_urls: false,
          valid_elements: '*[*]',
          content_css: '/assets/css/prism.css',
          plugins: ['searchreplace', 'table', 'textpattern', 'textcolor', 'anchor', 'hr', 'emoticons', 'lists advlist', 'codesample', 'link', 'autolink', 'image', 'imagetools', 'insertdatetime',
            'fullscreen', 'media', 'template', 'code', 'preview'],
          toolbar: 'searchreplace,table,sizeselect | bold italic | fontselect |  fontsizeselect,textpattern,forecolor backcolor,anchor,hr,emoticons,bold italic underline | bullist numlist outdent indent,alignleft aligncenter alignright alignjustify,codesample,link,autolink,image,imagetools,insertdatetime,fullscreen,media,template,code,preview'
        }
        ),
        HttpClientModule,
        HttpModule,
        FormsModule,
        ReactiveFormsModule,
        AgmCoreModule.forRoot({
          apiKey: 'AIzaSyCKHGctDoGx1_YdAbRsPlJYQqlQeC6kR2E'
        }),
        NgxCarouselModule,
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule,
        MatButtonModule,
        MatCheckboxModule,
        MatCardModule,
        MatInputModule,
        MatRadioModule,
        MatSelectModule,
        MatTabsModule,
        MatSortModule,
        MatPaginatorModule,
        MatTableModule,
        MatSnackBarModule,
        MatIconModule,
        MatDialogModule,
        MatAutocompleteModule,
        MatGridListModule,
        MatMenuModule,
        MatProgressBarModule,
        MatExpansionModule,
        MatTooltipModule,
        MatSlideToggleModule
      ],
      providers: [DataService,Util],
      bootstrap: [AppComponent],
      entryComponents: [ManageMenuComponent,ConfirmDeleteComponent]
    })
    export class AppModule { }
    
  6. Now, edit the src -> app -> client -> view-page -> view-page.component.css and replace its content with our famous CSS:  
    mat-card
    {
      margin: 0px;
      padding: 0px;
    }
    
    mat-card-noshadow{ 
        background: #ECECF4;
        box-shadow: none !important;
    }
    
    mat-card-content{
      margin: 0px;
      padding: 0px;
    }
    
    .header
    {
    background: #45484d;
    border-bottom: 5px solid #393B3E;
    height: 50px;
    padding-left: 5px;
    }
    
    .subheader
    {
    height: 40px;
    background: #5B5C60;
    }
    
    .subheader_title{
        vertical-align:baseline;
        padding-top: 5px;
        padding-bottom: 0px;
        padding-left: 5px;
        font-size: 13pt;
        font-family: Arial, Helvetica, sans-serif;
        color: #C8CCD2;
    }
    
    .header_title{
        vertical-align:baseline;
        padding-top: 10px;
        padding-bottom: 0px;
        padding-left: 5px;
        font-size: 16pt;
        font-family: Arial, Helvetica, sans-serif;
        color: #A1A9AF;
    }
    
    .card_cntnt
    {
        padding: 15px;
        padding-bottom: 15px;
    }
    .content_header
    {
        font-size:16pt;
        color: coral;
      
    }
    .content_subheader
    {
        color: #908482;
    }
  7. Next, src -> app -> client -> view-page -> view-page.component.html and replace its content with following:  
    <mat-card>
      <mat-card-header class="header">
        <mat-card-title class="header_title">{{page.Title}}</mat-card-title>
      </mat-card-header>
      <mat-card-content class="card_cntnt">
        <div [innerHTML]="page.Content | keepHtml"></div>
      </mat-card-content>
    </mat-card>
  8. You can see, the page title is coming from page object that is loading from the database, in <div [innerHTML]="page.Content | keepHtml"></div>, we are assigning the page.Content and using our newly created Pipe keepHtml to bypass the special script related characters to view everything to a user without HTML sanitization. 
  9. Edit the src -> app -> client -> view-page -> view-page.component.ts and replace its code with the following:  
    import { Component, OnInit } from '@angular/core';
    import { Router, ActivatedRoute } from "@angular/router";
    import { DataService } from "../../service/data/data.service";
    
    @Component({
      selector: 'app-view-page',
      templateUrl: './view-page.component.html',
      styleUrls: ['./view-page.component.css']
    })
    export class ViewPageComponent {
    
      GET_ALL_URL: string = "/api/page/id";
      page: any = {};
    
      constructor(private _dataService: DataService, private route: ActivatedRoute) {
    
         this.route.params.subscribe(
          params => {
            this.loadContentbyId(params['id']);
          });
      }
    
      loadContentbyId(id: string): void {
        let url = this.GET_ALL_URL.replace('id', id);
        this._dataService.get(url).subscribe(page => {
          this.page = page.data;
        });
      }
    }
  10. That's pretty simple code:
    1. On a top, we specify the API endpoint (/api/page/id) to load the page by menu ID
    2. In a constructor, we are creating the DataService and ActivatedRoute services object. The route object is used to get the [routerLink] parameter value passed to the ViewPage component. (check the app.component.html for reference). 
    3. The params['id'] is a menu ID that is passed to loadContentbyId() function to load the page from the database. 
    4. The loadContentbyId() function is taking menu ID as an input parameter, concatenating it with API URL and calling the data service GET method to load the page record from the database. The result is stored in a page object that we are using in view-page.component.html to show the page data on a screen.
  11. That's all for Part 9. Now, create different menu items, pages and check if you are successfully able to see them on screen. I am also attaching the small video for your view where first I will
    1. Go to the admin section.
    2. Go to Menus Management.
    3. Create a new menu.
    4. Go to Page Management.
    5. Create a simple page and save it.
    6. After refreshing the website, both menu and page would be visible on a screen. 


Keywords: angular reactive form tutorial, Node.js nodeemailer, MEAN stack tutorial, Angular 5 tutorial for beginners, MEAN Stack tutorial for beginners, rxjs tutorial or beginner, rxjs vs Promise,nodemailer,pm2, node.js MongoDB, node.js mongoose configuration, angular data grid control, free angular data grid control, tinymce angular, angular text editor, angular dynamic page, multer, mime-types, save files on storage in node.js