Create Angular App & SideBar

In the earlier tutorials, you learnt to create APIs in PHP with MYSQL database and Phinx, do authentication with JWT, and protect APIs.

Now you learn to create Angular client/front-end app to access the APIs. After completing the Angular tutorials, you build a full stack web application using Angular, PHP, and MYSQL.

Let start the first part of Angular tutorials by creating an Angular app and add side bar menu to the app. Create angcli14 folder in a drive of your choice. Enter the angcli14 and execute the following command to install Angular Cli version 14.0.0.

angcli14>npm install @angular/cli@14.0.0

Then run the command below to create ang14-client Angular app.

angcli14>ng new ang14-client

After the project finishes its initialization, enter the ang14-client folder, and execute the command below to run the app.

ang14-client>ng serve

By default Angular launches the app on port 4200. So you can access the app on your local machine via http://localhost:4200

Let install following dependencies that are necessary to build UI in Angular.

ang14-client>npm install @angular/cdk@13.0.0 @angular/material@13.0.0 material-icons

Open angular.json file to add a pre-built theme to the app.

........
  
"styles": [
              "./node_modules/@angular/material/prebuilt-themes/purple-green.css",
              "src/styles.css"
            ],
......

Update src/index.html file to use bootstrap and google fonts that are important in building side bar menu.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Ang App</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet">
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap"
rel="stylesheet">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

</head>
<body>
 
  <app-root></app-root>
</body>
</html>

Now we are ready to add a side bar component to the app. Run the command below to create SideBar component.

ang14-client>ng generate component SideBar

The side-bar folder is created. It contains all necessary files of the SideBar component. Update side-bar.component.ts to handle side bar behaviors, side-bar.component.html for side bar layout, and side-bar.component.css to style the layout. 

side-bar/side-bar.component.ts

import { Component, OnChanges, OnInit,SimpleChanges } from '@angular/core';
import { Input, Output, EventEmitter } from "@angular/core";
import { FlatTreeControl } from '@angular/cdk/tree';
import { MatTreeFlatDataSource, MatTreeFlattener } 
    from "@angular/material/tree";

interface Obj {
  name: string;
  link: string;
  children?: Obj[];
}
  
const OBJ_TREE: Obj[] = [
  {
    name: "Resources Management",
    link:"/resources",
    children: [
      { name: "Products", link:"products" },
      { name: "Categories", link:"/categories", children: [{ name: "Sub Categories", link:"/subcategories"}] },
      { name: "Csutomers", link:"/customers" },
      { name: "Orders", link:"/orders" },
     
    ],
  },
  {
    name: "Users Management",
    link:"/users",
    children: [{ name: "Users List", link:"/ulist" }, { name: "Roles", link:"/roles" }],
  },
];
  
/** Flat node with expandable and level information */
interface FlatNode {
  expandable: boolean;
  name: string;
  link: string;
  level: number;
}
@Component({
  selector: "side-nav",
  templateUrl: './side-bar.component.html',
  styleUrls: ['./side-bar.component.css']
})
export class SideBarComponent implements OnInit,OnChanges {

 
 // input property receiving value change from parent component (app.component)
 @Input() isExpanded: boolean=false;
 // Notify change to parent component (app.component)
 @Output() toggleMenu = new EventEmitter();

 public routeLinks = [
   { link: "about", name: "About", icon: "dashboard"},
   { link: "locations", name: "Locations", icon: "account_balance"},
 ];


 private _transformer = (node: Obj, level: number) => {
   return {
     expandable: !!node.children && node.children.length > 0,
     name: node.name,
     link: node.link,
     level: level,
   };
 };
 
 treeControl = new FlatTreeControl<FlatNode>(
   (node) => node.level,
   (node) => node.expandable
 );
 
 treeFlattener = new MatTreeFlattener(
   this._transformer,
   (node) => node.level,
   (node) => node.expandable,
   (node) => node.children
 );
 
 dataSource = new MatTreeFlatDataSource(
   this.treeControl, this.treeFlattener);
 
 constructor() {
   this.dataSource.data = OBJ_TREE;
 }
 
 hasChild = (_: number, 
   node: FlatNode) => node.expandable;

 ngOnInit(): void {
   
 }
 ngOnChanges(changes: SimpleChanges): void {
   
   if(!this.isExpanded){ // collapse tree side nav
       this.treeControl.collapseAll();
   }
 }

}

side-bar/side-bar.component.html
<section [class.sidenav]="isExpanded">
    <div class="toggle">
      <mat-icon  (click)="toggleMenu.emit(null)">
        {{ isExpanded ? "keyboard_backspace" : "dehaze" }}
      </mat-icon>
    </div>
 
    <mat-tree class="nav" [dataSource]="dataSource" 
    [treeControl]="treeControl">
    <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
        
      <!-- Adding disabled property to button 
          so as to give padding to nodes -->
      <button mat-icon-button disabled></button>
      <a 
        
        class="hover"
        routerLink="{{node.link}}"
      >
      <span  *ngIf="isExpanded">{{ node.name }}</span>
      </a>
      
    </mat-tree-node>

    <!-- Tree node template for expandable nodes -->
    <mat-tree-node 
      *matTreeNodeDef="let node;when: hasChild" 
      matTreeNodePadding>
      
      <button mat-icon-button matTreeNodeToggle 
          [attr.aria-label]=
            "'Toggle ' + node.name">
          <mat-icon class="mat-icon-rtl-mirror"
          [matTooltip]="!isExpanded ? node.name : ''"
          matTooltipPosition="right"
          > 
              {{treeControl.isExpanded(node) ? 
                  'expand_more' : 'chevron_right'}} 
          </mat-icon>
      </button>
      <a 
       
        class="hover"
        routerLink="{{node.link}}"
      >
      <span matLine *ngIf="isExpanded">{{ node.name }}</span>
    </a>
    </mat-tree-node>
    </mat-tree>

    <mat-list class="nav" *ngFor="let route of routeLinks">
      
      <a
        mat-list-item
        routerLinkActive="active-link"
        class="hover"
        routerLink="{{ route.link }}"
      >
        <mat-icon
          mat-list-icon
          [matTooltip]="!isExpanded ? route.name : '{}'"
          matTooltipPosition="right"
          
        >
          {{ route.icon }}</mat-icon
        >
        <span matLine *ngIf="isExpanded">{{ route.name }}</span>
      </a>
     
    </mat-list>
   

  </section>

side-bar/side-bar.component.css
.toggle {
    width: 100%;
    display: flex;
    justify-content: space-around;
    padding: 25px 0;
    &:hover {
      cursor: pointer;
    }
  }
  
  
    .active-link {
      background-color: #fafafa;
      border-left: solid 3px #1976d2;
    }
  
    .hover {
      transition-duration: 400ms;
      transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
      transition-property: transform, border, background-color;
    }
  
    mat-icon {
      color: #1976d2;
      cursor: pointer;
      
    }
  
  
  h1, h3 {
    color: green;
    font-family: "Roboto", sans-serif;
    text-align: center;
  }
    
  .mat-tree {
    background: transparent;
    padding-left: 10px!important;
  }
    
  .mat-tree-node {
    color: black;
    padding-right: 10px;
    font-size: 16px!important;
  }
  a{
    color: white;
  }
  

The @Input and @Output properties are means to communicate between child (SideBar) and parent (App) components. When the toggle button on SideBar is clicked, the toggleMenu() function of the parent component is triggered from the child component and the isExpanded property in App component changes that leads to change the isExpanded property in the child component.

App component is entry point of the Angular app. Update app.component.st, app.component.html, and app.component.css as shown below.

app/app.component.ts
import { Component } from '@angular/core';
import { Router } from '@angular/router';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  
  public isExpanded = false;
  constructor(private router: Router){  
    
  }
  public toggleMenu() {
    this.isExpanded = !this.isExpanded;
  }
  
  public isLoggedIn(){ 

    return false;

  }

  public logout(){
   
  }
}

app/app.component.html
<mat-sidenav-container autosize>
  <mat-sidenav
    #sidenav
    fixedInViewport="true"
    mode="side"
    opened="{{ isExpanded }}"
  >
    <side-nav
      (toggleMenu)="toggleMenu()"
      [isExpanded]="isExpanded"
    ></side-nav>
  </mat-sidenav>
  <mat-sidenav-content [style.margin-left.px]="!isExpanded ? 60 : 250">
    <div class="container">
      <nav class="navbar navbar-default">
        <div class="container-fluid">
          <div class="navbar-header">
            Admin Panel
          </div>
          <ul class="nav navbar-nav">
                 <li class="nav-item" *ngIf="!isLoggedIn()">  
                <a class="nav-link" routerLink="/login" routerLinkActive="active">Login</a>  
              </li>  
              <li class="nav-item" *ngIf="isLoggedIn()" (click)="logout()">  
                <a class="nav-link" routerLink="#" routerLinkActive="active">Logout</a>  
              </li>
          </ul>
        </div>
      </nav>
      
      <router-outlet></router-outlet>
    </div>
  </mat-sidenav-content>
</mat-sidenav-container>

app/app.component.css
@import 'material-icons/iconfont/outlined.css';
* {
    font-family: 'Gill Sans', 'Gill Sans MT', 'Trebuchet MS', sans-serif;
    box-sizing: border-box;
    font-size: 16px;
}
.sidebar-open{
    background-color: #080808;
    border: none;
    color: white;
    padding: 16px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;
    margin: 4px 2px;
    transition-duration: 0.4s;
    cursor: pointer;
}
.btnsidebar{
    background-color: black;
    color: white;
    border: 1px solid #555555; 
    border-radius: 10px;
}
.btnsidebar:hover{
    box-shadow: 2px 2px 5px gray;
} 
.sidebar-slider {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    background-color: black;
    transform: translateX(-100%);
    transition: ease-in-out 300ms transform;
    width: 300px;
}
.sidebar-slider.sidebar-slide-in {
    transform: translateX(0%);
    transition: ease-in-out 600ms transform;
}
.sidebar-close {
    position: fixed;
    top: 0;
    right: 0;
    color: black;
    cursor: pointer;
    background-color: white;
    border: none;
    margin: 7px;
    border-radius: 3px;
    padding-left: 10px;
    padding-right: 10px;
    font-size: 21px;
    font-weight: 400;
}
.sidebar-content {
    display: flex;
    flex-direction: column;
    color: white;
    font-size: 16px;
    padding: 10px;
    height: 100%;
    margin-top:66px;
    
}
.set-align{
    text-align: center;
}
.set-sidebar-text{
    padding: 11px;
}
.set-sidebar-text:hover{
    background-image: linear-gradient(to right, rgb(150, 218, 250),black);
    color: black;
    border-left: 10px solid rgb(3, 172, 250);
    cursor: pointer;
}
.container-fluid{
    background-color: #444444;


}
.navbar-nav li a{
    color: #fff;
    
}
.navbar-header{
   padding-left: 20%;
   padding-top: 15px;
}
.navbar-nav{
    float: right;
    border: 0px solid transparent !important;
}
.navbar{
    border: 0px solid transparent !important;
}
mat-sidenav[style] {
    visibility: visible !important;
  }
.mat-drawer {
    transform: none !important;
  }
.mat-drawer-content{
    display: inline !important;
}
Finally update app.module to add the following modules to the imports section.
...... 
imports: [
    ......
    FormsModule,
    HttpClientModule,
    MatTreeModule,
    MatSidenavModule,
    MatListModule,
    MatIconModule,
    MatTooltipModule,
    MatButtonModule,
    MatExpansionModule,
  ],
......

Save the Project. The app is refreshing!

Comments

Popular posts from this blog

APIs to Upload form data with file in PHP & MYSQL

PHP Mysql Database Migration Using Phinx