Fullstack Hub

[Count: 1]

In this part, we will mostly work on the Admin section. We will develop the Menu Management page where we can add, update, delete and view the website’s menu items. We will also develop our own Data Grid control that would be used in all future Admin pages e.g. manage contacts, pages, etc.
PS: The source code for this part is private and only available if you subscribe and create the account with Full Stack Hub!

Introduction

In this part, we will mostly work on the Admin section, we will create the Menu Management page where we would be able to see all Menu items and functionality to add, update and delete them. We will also create our own Data Grid Component by using multiple Angular Material UI components, this component would be used in all Admin pages to manage the website’s content.

Let’s Start

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

  1. Get the Part 6 from Fullstack GitHub, clone or download it locally. Run the mazharncoweb project by ng serve -o command and server project by pm2 start environment.json --env development command. Make sure there is no error, if you find any, please comment or send me an email.
  2. In the previous part, we already have created the POST, PUT and DELETE APIs in Node.js server application so we will only work on the client-side.
  3. Right click on the mazharncoweb folder and select the option Open in Terminal, enter the command: ng g c admin/admin to create the Admin page for Admin section, this would contain links to other management pages e.g. MenuUser Messages, and Page management. Verify that app folder has an admin component created in the admin folder. 
  4. Edit the app -> admin -> admin.component.css and add following CSS in it:  
mat-card
{
  margin: 0px;
  padding: 0px;
}

mat-card-noshadow{ 
    background: #ECECF4;
    box-shadow: none !important;
}

mat-card-content{
  margin: 0px;
  padding: 0px;
}

.header
{
background: #E90C0C;
border-bottom: 5px solid #790606;
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: #FFFFFF;
}

.card_cntnt
{
    padding: 15px;
    padding-bottom: 15px;
}

.card_footer
{
    text-align: left;
    padding-left: 40px;
}

.frm-ctrl {
    width: 90%;
}

.icon_align
{
    vertical-align: middle;
}
.phone_cntnt
{
    font-weight: bold;
}
​
  1. In above CSS, we are mostly fixing the Angular Material UI Card component header and title, I am purposely keeping the header background color red to highlight the Admin screen, feel free to play with CSS and change the font style and color.  
  2. Edit the app -> admin -> admin.component.html and replace the existing content with the following HTML:
<div>
  <mat-card>
    <mat-card-header class="header">
      <mat-card-title class="header_title"><i class="material-icons">security</i> Manage Site Content</mat-card-title>
    </mat-card-header>
    <mat-card-content class="card_cntnt">
      <div>
        <div>
          <button [routerLink]="['managemenu']" mat-button color="primary"><i class="material-icons">menu</i>    Menus Management</button>
        </div>
        <hr>
        <div>
          <button [routerLink]="['usermsg']" mat-button color="primary"><i class="material-icons">message</i>    User Messages</button>
        </div>
        <hr>
        <div>
            <button [routerLink]="['managepage']" mat-button color="primary"><i class="material-icons">pages</i>    Pages Management</button>
        </div>
      </div>
    </mat-card-content>
  </mat-card>
</div>​
  1. The above HTML is quite self-explanatory, we have Material Card with three buttons; Menu ManagementUser Messages, and Pages Management. The [routerLink] directive is holding the page route information, we will define these routes in the app-routing.module.ts routing table.
  2. Don’t worry about admin.component.ts since we are not adding any functionality here.
  3. Next, as discussed in step 7, let’s create the routes in the app-routing.module.ts file, edit the app -> app-routing.module.ts file and replace it content with following, you would see at the end, we added routes for admin and management for now, we will add rest of the routes later: 
TypeScript
  1. So far good, run the application and browse the http://localhost:4200/admin URL, you should land to Admin Index page with three links and a cool red color header. 
  2. The next step is the important one, we will create our custom grid that would take the input parameters and create the data grid with Add, Update and Delete operation. I am not going to explain the inner detail of the data grid since it needs a separate article to explain it in detail that I would write in future after converting it to the NPM package. 
  3. Go ahead and in maharncoweb terminal, run the command: ng g c shared/datagrid , you would see a new Datagrid folder in a shared folder with a new component. Edit the datagrid.component.html and replace its content with the following:
<div>
  <div class="row">
    <div class="col-md-10">
      <mat-form-field floatPlaceholder="never">
        <input matInput #filter placeholder="{{filterPlaceholder}}">
      </mat-form-field>
    </div>
    <div class="col-md-2 text-right">
      <button (click)="click(1,null)" *ngFor="let hb of hdrBtn" mat-raised-button color="primary">{{hb.title}}</button>
    </div>
  </div>
  <div></div>
  <div>
    <mat-table #table [dataSource]="dataSource" matSort>

      <ng-container *ngFor="let clmn of displayedColumns" matColumnDef="{{clmn.variable}}">
        <mat-header-cell *matHeaderCellDef mat-sort-header>{{clmn.display}}</mat-header-cell>
        <mat-cell *matCellDef="let element">
          <span *ngIf="clmn.type=='text'">{{element[clmn.variable]}}</span>
          <span *ngIf="clmn.type=='btn'" style="text-align: right"><button mat-raised-button color="warn" (click)="click(clmn.action,element)" >{{clmn.variable}}</button></span>
        </mat-cell>
      </ng-container>

      <mat-header-row *matHeaderRowDef="displayedColumnsVar"></mat-header-row>
      <mat-row *matRowDef="let row; columns: displayedColumnsVar;"></mat-row>
    </mat-table>
  </div>
  <div *ngIf="menudataObj && menudataObj.data.length==0">
    There is no recod available!
  </div>
  <mat-paginator #paginator [length]="menudataObj.data.length" [pageIndex]="0" [pageSize]="10" [pageSizeOptions]="[10,20,50,100]">
  </mat-paginator>
</div>​
  1. We are using the built-in Material UI Table and Paginator components that really help us to create the awesome grid with the minimum HTML code.
  2. Next, edit the datagrid.component.ts and replace its content with the following:    
TypeScript
  1. As promised, I would explain this code some other time because that will make this article too long and boring. Feel free to dig into it yourself.    
  2. We need to create one more class to facilitate the data grid, in the same mazharncoweb terminal, run the command:  ng g class shared/datagrid/grid-ops
  3. Edit the app -> shared -> datagrid -> grid-ops and replace its content with the following:   
TypeScript
  1. Most of the code, I took and modified from Angular Material UI Paginator and Sort components, you can also take a look there and try to make a sense of what is being modified in this class. 
  2. Next, let’s modify the enum.ts and add the CRUD operation enumeration, I also modified it to avoid hard-coded strings in the rest of the components. Edit the app -> shared -> enum.ts and replace its content with the following:
JSON
  1. You can see in the above file, we are introducing DBOperation enum that would be used to pass the parameters to the grid component, we won’t need to pass a hard-coded string to identify the operation. The same goes to other enumerations, you will see these enumerations in action in upcoming steps. 
  2. For all delete operations, e.g. delete a menu item, we want to create an extra layer of confirmation after viewing the record, a user wants to delete. Let’s create a delete confirmation component that would be used in all management page. In mazharncoweb terminal, enter the command: ng g c shared/messages/confirm-delete
  3. Edit the app -> shared -> messages -> confirm-delete.component.css and replace its content with the following:
.header{
    background: #E90C0C;
    border-bottom: 5px solid #790606;
    height: 50px;
    padding-left: 5px;
    }
  
    .header_title{
        vertical-align:baseline;
        padding-top: 10px;
        padding-bottom: 0px;
        padding-left: 5px;
        font-size: 16pt;
        font-family: Arial, Helvetica, sans-serif;
        color: rgba(255, 255, 255, 0.85);
    }
    mat-card{
      margin: 0px;
      padding: 0px;
  
    }
    mat-card-content{
      margin: 0px;
      padding: 0px;
   
      height: 170px;
      overflow:hidden;
    }
    
  .mat-dialog-container {
      padding: 0 !important;
      margin: 0 !important;
      }
  
  .frm-ctrl {
      width: 100%;
   }
  
   .card_cntnt
   {
       padding: 20px;
   }
   .msg
   {
       font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
       font-size: large;
    padding-top: 10px;
   }
   .footer
   {
       padding-top: 10px;
       text-align: center;
   }​
  1. Pretty much the same CSS we created for the Admin component, feel free to change it and make it more attractive.
  2. Edit the app -> shared -> messages -> confirm-delete.component.html and replace its content with the following:
<mat-card>
  <mat-card-header class="header">
    <mat-card-title class="header_title"><i class="material-icons">delete</i> Verify one more time!</mat-card-title>
  </mat-card-header>
  <mat-card-content class="card_cntnt">
    <div class="msg">
      <p><i class="material-icons">live_help</i> Are you sure to delete the selected record?</p>
    </div>
    <hr>
    <div class="footer">
      <button mat-raised-button color="primary" [mat-dialog-close]="true" tabindex="2">Yes</button>
      <button mat-raised-button color="warn" (click)="onNoClick()" tabindex="-1">No</button>
    </div>
  </mat-card-content>
</mat-card>​
  1. There are one verification message and two buttons at the bottom, this component would appear in the dialog box like traditional JavaScript confirm but with our custom style.
  2. Edit the app -> shared -> messages -> confirm-delete.component.ts and replace its content with the following:
TypeScript
  1. You can see we are using the Angular Material Dialog Ref component that is taking the Confirm Delete component reference which will open this component in the dialog box, that’s very simple, any component you want to open in the dialog box, you can use the same approach. 
  2. The onNoClick function is attached to the Cancel button and it simply hides the dialog box.
  3. Now we have Confirm Delete component ready to be used, let’s update the util.ts to create a function to open the dialog box. In management components e.g. Manu Management we will call a method from util.ts. Edit the app -> shared -> util.ts and replace its content with the following:  
TypeScript
  1. You can see, we added the confirmDelete function that would return true or false based on the user selection and then we will decide accordingly to delete the record or not. 
  2. If you remember our APIs client, we only had GET method, since we are creating a Menu Management component with CRUD operation, we need the POSTPUT and DELETE methods. Edit the app -> service -> data -> data.service.ts and replace its content as following:   
TypeScript
  1. Cool, so this is a pretty clean APIs client with all required methods. It is always wise to capture the error, that’s an assignment for you to read the latest documentation from the Angular official website and implement it. Though, this code would work absolutely fine with no error.
  2. In above service, we are using the HTTPClientModule that was not imported earlier so let’s add it in AppModule. Edit the app -> app.module.ts and replace its content with the following:   
TypeScript
  1. OK, so far we created the baseline for Menu Management components development, we need a data grid, Confirm Delete and updated APIs client with all CRUD HTTP verbs (PUT, POST and DELETE) that we created in previous steps, we are ready to create a Menu Management components. There would be two components for all manage components, one would have a data grid with all records and AddUpdate and Delete buttons. Second would be displayed in the dialog box where you can add new, Update and Delete existing records. 
  2. In mazharncoweb terminal, run the command: ng g c admin/menu/menuList , this component would have a data grid we created in previous steps. Edit the app -> admin -> menu -> menu-list.component.css and replace its content (if any) with following:  
mat-card
{
  margin: 0px;
  padding: 0px;
}

mat-card-noshadow{ 
    background: #ECECF4;
    box-shadow: none !important;
}

mat-card-content{
  margin: 0px;
  padding: 0px;
}

.header
{
background: #E90C0C;
border-bottom: 5px solid #790606;
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: #FFFFFF;
}

.card_cntnt
{
    padding: 15px;
    padding-bottom: 15px;
}

.card_footer
{
    text-align: left;
    padding-left: 40px;
}

.frm-ctrl {
    width: 90%;
}

.icon_align
{
    vertical-align: middle;
}
.phone_cntnt
{
    font-weight: bold;
}
​
  1. This is again our famous CSS, no need to explain it again.
  2. Next, let’s edit the app -> admin -> menu -> menu-list.component.html and replace its contents with the following:
<div>
  <mat-card>
    <mat-card-header class="header">
      <mat-card-title class="header_title"><i class="material-icons">menu</i> Menus Management</mat-card-title>
    </mat-card-header>
    <mat-card-content class="card_cntnt">
      <div>
        <app-datagrid
        [displayedColumns]="displayedColumns"
        [filterPlaceholder]="filterPlaceholder"
        [hdrBtn]="hdrBtn"
        [data]="data"
        (btnclick)="gridaction($event)"
        ></app-datagrid>
      </div>
    </mat-card-content>
  </mat-card>
</div>
<button [routerLink]="['../../admin']" mat-button color="primary">Back to Menu</button>​
  1. In the above HTML, we are using our newly created data grid and passing the values to its properties that are defined in a menu-list.component.ts file. It looks pretty clean to me, the properties name somehow gives information about their purpose but it would be more clear in component’s typescript. 
  2. Edit the app -> admin -> menu -> menu-list.component.ts and replace its content with the following: 
TypeScript
  1. This is an important code to understand so let’s dig into it a little bit:
    1. dbops: DBOperation: Here we are creating DBOpertaion enumeration type variable dbops so that we don’t use hard-coded string to represent CRUD operation.
    2. menu: IMenu: I am a big fan of MVC architecture so I always like to declare the interface or class for Model. We created the IMenu in the previous article to represent the menu records returned from the database and match the data schema.
    3. displayedColumns: This is one of the input variables to the data grid, it is an object containing grid column title, corresponding variable (the JSON response object’s key name), data type and for last two-button type items, we are also specifying the action i.e. update and delete. 
    4. hdrBtn: Also an input variable to the data grid, it contains a list of all buttons that would be displayed on top of the data grid. Currently, it has only one Add New Menu button.
    5. loadData(): This method will load all menu records from the database and store it in data variable, this method is being called from ngOnInit() event that triggers as soon page is being initiated.   
    6. gridaction(): This is an output method of the data grid, in a displayedColumns object, you can see we are passing the action for button type items. In data grid when a user would click on AddEdit or Update button, this action would be returned back to gridaction()‘s event argument along the complete data row from where we can get data ID to identify which record needs to be updated or deleted, of course for add operation, we only need action and not a data row. In the function body, you can see, we have a switch statement on the action and calling the corresponding method based on action and passing the data row in case of the update and delete.
    7. CRUD FUnctionsaddMenu()editMenu() and deleteMenu() are initializing the dialog box header and button title, edit and delete menu are also getting the data row return from data grid and assigning it to IMenu interface casted menu variable that would be used to show the data to a user before updating or deleting. All function calls one common method openDialog at the end.
    8. openDialog(): This method will open the CRUD screens in the dialog box, we will create the CRUD component in the next step. It also passes the data to the Manage Menu (CRUD) component that we initialize in Add, Edit and Deletes Menu functions. In the end, we are attaching the afterClosed event to update the data in the data grid as soon the user performs any CRUD operation. This would avoid any refresh button. 
  2. Now let’s create a Manage Menu component where the user can actually perform CRUD operation on Menu, in mazharncoweb terminal run the command: ng g c admin/menu/manageMenu 
  3. Edit the app -> admin -> menu -> manage-menu -> manage-menu.component.css and replace (if any) text with following:
.header{
    background: #E90C0C;
    border-bottom: 5px solid #790606;
    height: 50px;
    padding-left: 5px;
    }
  
    .header_title{
        vertical-align:baseline;
        padding-top: 10px;
        padding-bottom: 0px;
        padding-left: 5px;
        font-size: 16pt;
        font-family: Arial, Helvetica, sans-serif;
        color: rgba(255, 255, 255, 0.85);
    }
    mat-card{
      margin: 0px;
      padding: 0px;
  
    }
    mat-card-content{
      margin: 0px;
      padding: 0px;
      width: 600px;
      height: 500px;
    }
    
  .mat-dialog-container {
      padding: 0 !important;
      margin: 0 !important;
      }
  
  .frm-ctrl {
      width: 100%;
   }
  
   .card_cntnt
   {
       padding: 20px;
   }
   .footer
   {
       padding-top: 20px;
       text-align: right;
   }​
  1. Next edit the app -> admin -> menu -> manage-menu -> manage-menu.component.html and replace its content with the following:
<mat-card>
  <mat-card-header class="header">
    <mat-card-title class="header_title"><i class="material-icons">menu</i> {{modalTitle}}</mat-card-title>
  </mat-card-header>
  <mat-card-content class="card_cntnt">
    <div>
      <form novalidate (ngSubmit)="onSubmit(menuFrm)" [formGroup]="menuFrm">
        <div>
          <mat-form-field class="frm-ctrl">
            <input matInput placeholder="Menu Name" formControlName="MenuName">
            <mat-error *ngIf="menuFrm.controls['MenuName'].errors?.required">
                Menu Name is required!
            </mat-error>
          </mat-form-field>
        </div>
        <div>
          <mat-form-field class="frm-ctrl">
            <input matInput placeholder="Menu Code" formControlName="MenuCode">
            <mat-error *ngIf="menuFrm.controls['MenuCode'].errors?.required">
                Menu Code is required!
            </mat-error>
          </mat-form-field>
        </div>
        <div>
          <mat-form-field class="frm-ctrl">
            <input matInput placeholder="Menu URL" formControlName="MenuUrl">
            <mat-error *ngIf="menuFrm.controls['MenuUrl'].errors?.required">
                Menu Url is required!
            </mat-error>
          </mat-form-field>
        </div>
        <div>
          <mat-form-field class="frm-ctrl">
            <input matInput placeholder="Menu Order" formControlName="MenuOrder">
            <mat-error *ngIf="menuFrm.controls['MenuOrder'].errors?.required">
                Menu Order is required!
            </mat-error>
          </mat-form-field>
        </div>
        <div class="frm-ctrl">
          <mat-form-field class="frm-ctrl">
            <input matInput placeholder="Group Name" formControlName="GroupName">
            <mat-error *ngIf="menuFrm.controls['GroupName'].errors?.required">
                Group Name is required!
            </mat-error>
          </mat-form-field>
        </div>

        <div>
          <mat-radio-group formControlName="MenuType" class="frm-ctrl">
            <mat-radio-button *ngFor="let mtype of menuType" [value]="mtype">
              {{mtype}}
            </mat-radio-button>
          </mat-radio-group>
        </div>

        <div class="frm-ctrl">
          <mat-radio-group formControlName="Status" class="frm-ctrl">
            <mat-radio-button *ngFor="let sts of status" [value]="sts">
              {{sts}}
            </mat-radio-button>
          </mat-radio-group>
        </div>

        <div>
          <mat-form-field class="frm-ctrl">
            <mat-select placeholder="Parent Menu Code" formControlName="ParentMenuCode">
              <mat-option *ngFor="let menu of menuDDL" [value]="menu.key">
                {{ menu.value }}
              </mat-option>
            </mat-select>
          </mat-form-field>
        </div>
        <div class="footer">
          <button color="warn" type="button" mat-raised-button (click)="dialogRef.close()">Cancel</button> 
          <button type="submit" color="primary" [disabled]="menuFrm.invalid" mat-raised-button>{{modalBtnTitle}}</button></div>
      </form>
    </div>
  </mat-card-content>
</mat-card>​
  1. You can go through this code, quite easy to understand, we are using the Model Driven Form and specifying all the input fields including, text boxes, radio buttons, and dropdown lists. The model header and button title are coming from MenuList the component as described in previous steps, just to remind you, this component would be opened in the dialog box. 
  2. Edit the app -> admin -> menu -> manage-menu -> manage-menu.component.ts and replace its content with the following:
TypeScript
  1. Let’s briefly go through the code:
    1. At the start of the component, there are a couple of API URLs to load menu items from the database. 
    2. ngOnInit(): As I told you, I am a big fan of the Angular Model-Driven form, here we are creating the menuFrm and specifying the form elements, initial value, and validation rules. Also, we are initializing the form elements values sent from menu-list items in case of the update and delete operations. 
    3. onSubmit(): As soon as the user will submit the form, the control will call this method where we are calling the POSTPUT or DELETE services from a data.service.ts file that we extended in previous steps. One interesting step is in delete operation where we are first calling the confirmDelete method from a util.ts class that will open another confirmation dialog box and would return yes, no based on user action. 
    4. The rest of the methods are to load the drop-down values form a database or perform any helping functionality. 
  2. Let’s fix one cosmetic thing, I don’t like the extra spacing and margin in Angular Material UI dialog box, so let’s edit the app -> app.component.ts and add following CSS class on top of the file: 
.mat-dialog-container {
  padding: 0 !important;
  margin: 0 !important;
  }​
  1. Since we used the dialog box, so components in the dialog box should be added in AppModuleentryComponents sections since they are not dynamically part of DOM like other components and we explicitly have to load them. Other components get loaded through route etc. Edit the app -> app.module.ts file and replace its content with the following:
TypeScript
  1. One last thing we have to fix is the Contact component because we introduced enumeration instead of a hard-coded string, edit the app -> client -> contact -> contact.component.ts:   
TypeScript
  1. That’s all for now, run the application and browse the URL http://localhost:4200/admin , then click on Menu Management, you would land the http://localhost:4200/admin/managemenu page. You should have at least one record there if you correctly follow my last article properly, otherwise, go ahead, click on Add New Menu button, fill up all information and save it, try to edit and delete it after. 
  2. Let me know if you have any issue in any step.

Yaseer Mumtaz

Leave a Reply

Your email address will not be published. Required fields are marked *