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.
Update src/index.html file to use bootstrap and google fonts that are important in building side bar menu.
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; }
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; }
Comments
Post a Comment