
In the past few days of articles, we have learned a lot about angular . This time we will come up with a small product.
angualr is combined with ng-zorro to quickly and standardizedly develop a backend system. [Related tutorial recommendation: "Angular Tutorial"]
System functions include the following:
all services use simulated data.
Let's do it.
The more popular ui angular
combined with ng-zorro
angular include:Ant Design believe those who do front-end development are familiar with it. So here we combine it with the NG-ZORRO framework. If you are familiar with Vue or React version of Ant Design , I believe you can connect seamlessly ~

We reuse angular-cli to generate a project ng-zorro .
Adding ng-zorro is very simple: enter ng-zorro root directory and execute ng add ng-zorro-antd .
Of course, you can also execute
npm install ng-zorro-antdto add it, but it is not recommended.
After combining ng-zorro , we run the project npm run start , and you will see the following picture on the page http://localhost:4200 .

Not Bad, Bro.
We changedthe configuration routing
to hash routing and added user routing. The scaffolding has done it for us. We only need to make a few minor modifications.
Idea:
First add the page user list page, use table component in ng-zorro
to add and change the user's page to share the same page, use form ng-zorro
page deletion function in ng-zorro to directly use the pop-up prompt, use ng-zorro modal component in ng-zorro
introduces ng-zorro component as needed to
adjust the routing file.
According to the idea, we have to introduce it in ng-zorro :
// app.module.ts
import { ReactiveFormsModule } from '@angular/forms';
import { NzTableModule } from 'ng-zorro-antd/table';
import { NzModalModule } from 'ng-zorro-antd/modal';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzFormModule } from 'ng-zorro-antd/form';
import { NzInputModule } from 'ng-zorro-antd/input';
// ...
imports: [ // Add it in imports instead of declaring NzTableModule in declarations,
NzModalModule,
NzButtonModule,
NzFormModule,
ReactiveFormsModule,
NzInputModule
], simple and easy to understand principle, we do not use children to nest routing:
// app.routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
import { WelcomeComponent } from './pages/welcome/welcome.component';
import { UserComponent } from './pages/user/user.component';
import { UserInfoComponent } from './pages/user/user-info/user-info.component';
//Related routes const routes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: '/welcome'
},
{
path: 'welcome',
component: WelcomeComponent
},
{
path: 'user',
component: UserComponent
},
{
path: 'user/add',
component: UserInfoComponent
},
{
path: 'user/edit/:uuid',
component: UserInfoComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(
routes,
{
useHash: true,//Use hash mode preloadingStrategy: PreloadAllModules
}
)],
exports: [RouterModule]
})
export class AppRoutingModule { } Change menu
The menu generated using scaffolding does not match the functions we need to develop. Let's adjust it.
// app.component.html
<nz-layout class="app-layout">
<nz-sider class="menu-sidebar"
nzCollapsible
nzWidth="256px"
nzBreakpoint="md"
[(nzCollapsed)]="isCollapsed"
[nzTrigger]="null">
<div class="sidebar-logo">
<!-- By default, click on the logo to jump to the homepage -->
<a routerLink="/welcome">
<img src="https://ng.ant.design/assets/img/logo.svg" alt="logo">
<h1>Ng-Zorro</h1>
</a>
</div>
<ul nz-menu nzTheme="dark" nzMode="inline" [nzInlineCollapsed]="isCollapsed">
<li nz-submenu nzOpen nzTitle="User Management" nzIcon="dashboard">
<ul>
<li nz-menu-item nzMatchRouter>
<a routerLink="/user">User list</a>
</li>
</ul>
</li>
</ul>
</nz-sider>
<nz-layout>
<nz-header>
<div class="app-header">
<span class="header-trigger" (click)="isCollapsed = !isCollapsed">
<i class="trigger"
nz-icon
[nzType]="isCollapsed ? 'menu-unfold' : 'menu-fold'"
></i>
</span>
</div>
</nz-header>
<nz-content>
<div class="inner-content">
<router-outlet></router-outlet>
</div>
</nz-content>
</nz-layout>
</nz-layout> Menu display, if we need to do permission management, we need the backend to cooperate with the value transfer. Then we render the relevant permission menu to the page
and replace it with the above code. The basic skeleton obtained is as follows :

Complete the user list.
Next, complete the skeleton of the user list. Because we use the UI framework, it is extremely convenient for us to write:
Get the user list
// user.component.html
<nz-table #basicTable [nzData]="list">
<thead>
<tr>
<th>Name</th>
<th>Position</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<!-- Traverse the obtained data -->
<tr *ngFor="let data of basicTable.data">
<td>{{data.name}}</td>
<td>{{data.position}}</td>
<td>
<a style="color: #f00;">Delete</a>
</td>
</tr>
</tbody>
</nz-table> We simulated some data in the assets folder user.json :
{
"users": [
{
"uuid": 1,
"name": "Jimmy",
"position": "Frontend"
},
{
"uuid": 2,
"name": "Jim",
"position": "Backend"
}
],
"environment": "development"
} After writing the service, we call to get the user's data:
// user.component.ts
import { Component, OnInit } from '@angular/core';
import { UserService } from 'src/app/services/user.service';
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.scss']
})
export class UserComponent implements OnInit {
public list: any = []
constructor(
private readonly userService: UserService
) { }
ngOnInit(): void {
if(localStorage.getItem('users')) {
let obj = localStorage.getItem('users') || '{}'
this.list = JSON.parse(obj)
} else {
this.getList()
}
}
// Get the user list getList() {
this.userService.getUserList().subscribe({
next: (data: any) => {
localStorage.setItem('users', JSON.stringify(data.users))
this.list = data.users
},
error: (error: any) => {
console.log(error)
}
})
}
} Because no back-end service is introduced, here we use localstorage to record the status.
After completing the above, we get the list information as follows:

To add users and edit users,
we simply create a form, which contains only two fields, namely name and position . These two functions share a form ~
we add it in html :
// user-info.component.html
<form nz-form [formGroup]="validateForm" class="login-form" (ngSubmit)="submitForm()">
<nz-form-item>
<nz-form-control nzErrorTip="Please enter username!">
<input type="text" nz-input formControlName="username" placeholder="Please enter username" style="width: 160px;" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control nzErrorTip="Please enter a position!">
<input type="text" nz-input formControlName="position" placeholder="Please enter the position" style="width: 160px;"/>
</nz-form-control>
</nz-form-item>
<button nz-button class="login-form-button login-form-margin" [nzType]="'primary'">Confirm</button>
</form> The page looks like this:

Then there is the logical judgment to add or modify. If the connection is marked with uuid , it means editing, show you the codes .
// user-info.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, ParamMap } from '@angular/router';
@Component({
selector: 'app-user-info',
templateUrl: './user-info.component.html',
styleUrls: ['./user-info.component.scss']
})
export class UserInfoComponent implements OnInit {
public isAdd: boolean = true;
public userInfo: any = []
public uuid: number = 0;
validateForm!: FormGroup;
constructor(
private fb: FormBuilder,
private route: ActivatedRoute,
) { }
ngOnInit(): void {
this.userInfo = JSON.parse(localStorage.getItem('users') || '[]')
this.route.paramMap.subscribe((params: ParamMap)=>{
this.uuid = parseInt(params.get('uuid') || '0')
})
// It is the editing state, set the identifier if(this.uuid) {
this.isAdd = false
}
if(this.isAdd) {
this.validateForm = this.fb.group({
username: [null, [Validators.required]],
position: [null, [Validators.required]]
});
} else {
let current = (this.userInfo.filter((item: any) => item.uuid === this.uuid))[0] || {}
// Information backfill this.validateForm = this.fb.group({
username: [current.name, [Validators.required]],
position: [current.position, [Validators.required]]
})
}
}
submitForm() {
// If it does not comply with the submission, an error will be reported if(!this.validateForm.valid) {
Object.values(this.validateForm.controls).forEach((control: any) => {
if(control?.invalid) {
control?.markAsDirty();
control?.updateValueAndValidity({ onlySelf: true });
}
})
return
}
// Get the form data const data = this.validateForm.value
//Add new user if(this.isAdd) {
let lastOne = (this.userInfo.length > 0 ? this.userInfo[this.userInfo.length-1] : {});
this.userInfo.push({
uuid: (lastOne.uuid ? (lastOne.uuid + 1) : 1),
name: data.username,
position: data.position
})
localStorage.setItem('users', JSON.stringify(this.userInfo))
} else { //Edit user, update information let mapList = this.userInfo.map((item: any) => {
if(item.uuid === this.uuid) {
return {
uuid: this.uuid,
name: data.username,
position: data.position
}
}
return item
})
localStorage.setItem('users', JSON.stringify(mapList))
}
}
} We first set an identifier isAdd , which defaults to a new user; when uuid exists, set it to a false value, indicating that it is in an editing state and backfills the form with the content. The operation of submitting a form is also judged according to this identifier. We directly change the localStorage information to ensure synchronization of the list information.
For the deletion function,
we introduce a modal dialog box to ask whether to delete.
// user.component.ts
// Delete delete(data: any) {
this.modal.confirm({
nzTitle: '<i>Do you want to delete this user?</i>',
nzOnOk: () => {
let users = JSON.parse(localStorage.getItem('users') || '[]');
let filterList = users.filter((item: any) => item.uuid !== data.uuid);
localStorage.setItem('users', JSON.stringify(filterList));
this.list = filterList
}
});
} 
We find the deleted data, remove it, re-cache the new user data, and update table 's user list data.
So, so far, we have successfully completed a simple project. Let’s take a look at it as a whole using Gif .

【over】