七。Angular — 小Demo

登录(用户登录、退出、访问控制)员工管理(CRUD)

Posted by Azr on February 2, 2019

技术栈

  1. 框架: Angular
  2. UI: NG-ZORRO
  3. server: json-server + JWT权限认证

项目搭建步骤

  1. 创建项目ng new demo-hmr
  2. cd demo-hmr
  3. 使用antdng add ng-zorro-antd
  4. ng serve --open

配置路由

单页面应用程序,要先去配置路由。先理清楚,组件之间的关系。

  1. 创建要使用的组件

    $ ng g c login
    $ ng g c home
    
  2. app.module.ts 引入路由,设置路由规则,配置路由模块。

    html中指定路由出口指定导航链接。

  3. 为了方便后期的管理。创建一个路由的模块。

       $ ng g m app-routing
    
  4. 基本就是将app.module.ts中的引入路由,设置路由规则,配置路由模块复制至app-routing.module.ts中。

  5. 报错

    报错以下信息,就是在app-routing.module.ts中没有进行导出理由模块。

    compiler.js:1021 Uncaught Error: Template parse errors:
    'router-outlet' is not a known element:
    1. If 'router-outlet' is an Angular component, then verify that it is part of this module.
    2. If 'router-outlet' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("
    <a routerLink = '/login'>login</a>
    
  6. 代码如下

    app.module.ts

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
       
    import { AppComponent } from './app.component';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { FormsModule } from '@angular/forms';
    import { HttpClientModule } from '@angular/common/http';
    import { NgZorroAntdModule, NZ_I18N, zh_CN } from 'ng-zorro-antd';
    import { registerLocaleData } from '@angular/common';
    import zh from '@angular/common/locales/zh';
       
    import { LoginComponent } from './login/login.component';
    import { HomeComponent } from './home/home.component'
    import { AppRoutingModule } from './app-routing/app-routing.module'
       
       
    registerLocaleData(zh);
       
    @NgModule({
      declarations: [
        AppComponent,
        LoginComponent,
        HomeComponent
      ],
      imports: [
        BrowserModule,
        BrowserAnimationsModule,
        FormsModule,
        HttpClientModule,
        NgZorroAntdModule,
        AppRoutingModule
      ],
      providers: [{ provide: NZ_I18N, useValue: zh_CN }],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
       
    

    app-routing/app-routing.module.ts

    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
       
    import { RouterModule, Routes } from '@angular/router';
       
    import { LoginComponent } from '../login/login.component';
    import { HomeComponent } from '../home/home.component';
       
    const appRoutes: Routes = [
      { path: 'home', component: HomeComponent },
      { path: 'login', component: LoginComponent }
    ]
       
    @NgModule({
      imports: [
        CommonModule,
        RouterModule.forRoot(appRoutes)
      ],
      exports: [
        RouterModule
      ],
      declarations: []
    })
    export class AppRoutingModule { }
    

登陆功能实现

① 表单结构
  1. UI样式采用的NG-ZORRO中的登陆框。

  2. 居中采用NG-ZORRO中的栅格。

报错

Uncaught Error: Template parse errors:
Can't bind to 'formGroup' since it isn't a known property of 'form'. 
.... 

就是没有导入ReactiveFormsModule,需要在app.module.ts中进行导入导出。

import { ReactiveFormsModule } from '@angular/forms';
... 
@NgModule({
  declarations.....,
  imports: [ ReactiveFormsModule ]
})

代码:

login/login.component.html

<div nz-row class="losin-wrapper" nzType="flex" nzJustify="space-around" nzAlign="middle">
  <form nz-form [formGroup]="validateForm" class="login-form" (ngSubmit)="submitForm()">
    <nz-form-item>
      <nz-form-control>
        <nz-input-group [nzPrefix]="prefixUser">
          <input type="text" nz-input formControlName="userName" placeholder="账号">
        </nz-input-group>
        <nz-form-explain *ngIf="validateForm.get('userName').dirty && validateForm.get('userName').errors">Please input your username!</nz-form-explain>
      </nz-form-control>
    </nz-form-item>
    <nz-form-item>
      <nz-form-control>
        <nz-input-group [nzPrefix]="prefixLock">
          <input type="password" nz-input formControlName="password" placeholder="密码">
        </nz-input-group>
        <nz-form-explain *ngIf="validateForm.get('password').dirty && validateForm.get('password').errors">Please input your Password!</nz-form-explain>
      </nz-form-control>
    </nz-form-item>
    <nz-form-item>
      <nz-form-control>
        <label nz-checkbox formControlName="remember">
          <span>记住我</span>
        </label>
        <a class="login-form-forgot" class="login-form-forgot">忘记密码</a>
        <button nz-button class="login-form-button" [nzType]="'primary'">登陆</button>
        或者
        <a href="">注册</a>
      </nz-form-control>
    </nz-form-item>
  </form>
  <ng-template #prefixUser><i nz-icon type="user"></i></ng-template>
  <ng-template #prefixLock><i nz-icon type="lock"></i></ng-template>
</div>

login/login.component.ts

import { Component, OnInit } from '@angular/core';

import {
  AbstractControl,
  FormBuilder,
  FormGroup,
  Validators
} from '@angular/forms';


@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  validateForm: FormGroup;

  submitForm(): void {
    for (const i in this.validateForm.controls) {
      this.validateForm.controls[ i ].markAsDirty();
      this.validateForm.controls[ i ].updateValueAndValidity();
    }
  }

  constructor(private fb: FormBuilder) {
  }

  ngOnInit(): void {
    this.validateForm = this.fb.group({
      userName: [ null, [ Validators.required ] ],
      password: [ null, [ Validators.required ] ],
      remember: [ true ]
    });
  }
}

######② 登陆样式

login/login.component.css

.losin-wrapper {
  width: 100%;
  height: 100%;
  background-image: url(https://ws3.sinaimg.cn/large/006tNc79ly1g02hyg0a1kj31lm0u075z.jpg);
  background-size: cover;
}

.login-form {
  max-width: 300px;
}

.login-form-forgot {
  float: right;
}

.login-form-button {
  width: 100%;
}
③ 表单验证
  1. 在ts与html中,将validateForm更换为loginForm

  2. 不需要记住我的功能

    ngOnInit(): void {
        this.loginForm = this.fb.group({
          userName: [ null, [ Validators.required ] ],
          password: [ null, [ Validators.required ] ],
          //  delete
          remember: [ true ]
        });
      }
    
    <!-- delete formControlName="remember" -->
    <label nz-checkbox formControlName="remember">
        <span>记住我</span>
    </label>
    
  3. 进行账户验证

    1. 用户不输入内容显示’请输入用户名’
    2. 用户长度为3-6

    进行密码验证

    1. 用户不输入内容显示’请输入账户’
    2. 密码长度为6-8,只能由数字/字母组成

    代码

    login/login.component.ts

    ngOnInit(): void {
        this.loginForm = this.fb.group({
          userName: [ null, [ Validators.required, Validators.minLength(3), Validators.maxLength(6) ] ],
          password: [ null, [ Validators.required, Validators.pattern(/^[a-zA-Z0-9]{6,8}$/) ] ]
        });
      }
    

    login/login.component.html

        <nz-form-item>
          <nz-form-control>
            <nz-input-group [nzPrefix]="prefixUser">
              <input type="text" nz-input formControlName="userName" placeholder="账号">
            </nz-input-group>
            <nz-form-explain *ngIf="loginForm.get('userName').dirty && loginForm.get('userName').hasError('required')">请输入账户</nz-form-explain>
            <nz-form-explain *ngIf="loginForm.get('userName').dirty && loginForm.get('userName').hasError('minlength') || loginForm.get('userName').hasError('maxlength')">长度为3-6位</nz-form-explain>
          </nz-form-control>
        </nz-form-item>
    	<nz-form-item>
          <nz-form-control>
            <nz-input-group nzPrefixIcon="anticon anticon-lock">
              <input type="password" nz-input formControlName="password" placeholder="密码">
            </nz-input-group>
            <nz-form-explain *ngIf="loginForm.get('password').dirty && loginForm.get('password').hasError('required')">请输入密码</nz-form-explain>
            <nz-form-explain *ngIf="loginForm.get('password').dirty && loginForm.get('password').hasError('pattern')">密码只能由数字、字母组成,长度为6-8位</nz-form-explain>
          </nz-form-control>
        </nz-form-item>
    
  4. 判断是否验证成功

      submitForm(): void {
        for (const i in this.loginForm.controls) {
          this.loginForm.controls[ i ].markAsDirty();
          this.loginForm.controls[ i ].updateValueAndValidity();
        }
       
        // 添加验证 
             
        // if(this.loginForm.valid) {
        //   console.log('验证成功')
        // }else {
        //   console.log('验证失败')
        // }
             
        if(!this.loginForm.valid) {
          console.log('验证失败')
          return 
        }
        console.log('验证成功', this.loginForm.value)
      }
    

    当什么都不输入的时候,就会验证失败。

    验证失败

    输入一个正确的,就会验证成功。

    验证成功

  5. 因有重复的变量,简化
      submitForm(): void {
        // 设置变量,简化代码
        const loginForm = this.loginForm
        const { controls } = loginForm
       
        for (const i in controls) {
          controls[ i ].markAsDirty();
          controls[ i ].updateValueAndValidity();
        }
       
        if(!loginForm.valid) {
          console.log('验证失败')
          return 
        }
        console.log('验证成功', loginForm.value)
      }
    
④ 登陆功能
  1. 创建服务文件
$ ng g s login/login
  1. 导入HttpClient
  2. 根据文档写登陆的login,记得写interface
import { Injectable } from '@angular/core';
// 导入HttpClient
import { HttpClient } from '@angular/common/http';

// interface
interface loginForm {
  username: string,
  password: string
}

@Injectable({
  providedIn: 'root'
})

export class LoginService {

  constructor(private http: HttpClient) { }

  // 登陆的login
  login(loginForm: loginForm) {
    return this.http.post('http://localhost:2080/tokens', loginForm)
  }
}
  1. login/login.component.ts中导入登陆服务
import { LoginService } from './login.service';
.....
export class LoginComponent implements OnInit {

  constructor(private fb: FormBuilder, private LoginService: LoginService) {
  }

  loginForm: FormGroup;

  submitForm(): void {
	...
    if(!loginForm.valid) {
      console.log('验证失败')
      return 
    }
    console.log('验证成功', loginForm.value)
    const { userName, password } = loginForm.value
    const loginParams = {
      username: userName,
      password
    }
    this.LoginService.login(loginParams)
        .subscribe(res=> {
        this.router.navigate(['/home'])
    })
  }

}
  1. 测试,输入账号zqran密码123456

  1. 报错下图是username格式读取错误

⑤ 抽离类型和配置

类型

  1. 新建文件login.type.ts

    export interface LoginForm {
      username: string
      password: string
    }
    
  2. login.service.ts中替换interface LoginForm

  3. login.component.ts中的loginParams添加类型检查

配置

  1. app目录创建config.js

    export const URL = 'http://localhost:2080'
    
  2. login.service.ts导入配置,进行相应的更改

    login(loginForm: LoginForm) {
        return this.http.post(`${URL}/tokens`, loginForm)
      }
    

登陆访问控制

① 路由守卫
  1. 应用情景

    1. CanActivate ==> 导航到某路由的情况
    2. CanActivateChild ==> 导航到某子路由的情况
    3. CanDeactivate ==> 从当前路由离开的情况
    4. Resolve ==> 在路由激活之前获取路由数据
    5. CanLoad ==> 异步导航到某特性模块的情况
  2. 路由守卫实现访问控制思路

    1. 登录成功后,将 token 存储在 localStorage

    2. 在路由守卫CanActivate中判断是否有token

    3. 如果有直接放行。如果没有,跳转到 login 页

      注意:不要校验 login 页

  3. 路由守卫使用步骤

    1. 创建AuthGuard ng g guard auth
    2. 在路由模块中添加导入 auth guard
    3. home 路由添加 canActivate 守卫
② 路由导航守卫
  1. 存储token,记得添加一个关于token的类型检查

    login/login.component.ts

    .....
    // token 类型检查
    interface Token {
      token: string
    }
    ....
    export class LoginComponent implements OnInit {
    ......
         
        this.LoginService.login(loginParams)
    		// token 类型检查
            .subscribe((res: Token)=> {
              // console.log(res)
              // 存储token
              localStorage.setItem('login-token', res.token)
              this.router.navigate(['/home'])
            })
    ........
      }
    }
    
  2. 创建AuthGuard,删除掉不需要的功能

    $ ng g guard auth
    

    auth.guard.ts

    import { Injectable } from '@angular/core';
    import { CanActivate } from '@angular/router';
       
    @Injectable({
      providedIn: 'root'
    })
    export class AuthGuard implements CanActivate {
      canActivate(): boolean {
        console.log('路由守卫')
        return true;
      }
    }
    
  3. 添加理由守卫

    app-routing/app-routing.module.ts

    const appRoutes: Routes = [
      { path: 'home', component: HomeComponent, canActivate: [AuthGuard],},
      { path: 'login', component: LoginComponent }
    ]
    
  4. 测试。

    auth.guard.tsAuthGuard中的return true更改为return false,访问http://localhost:4200/home 会自动跳转到http://localhost:4200/.

  5. 获取token

  6. 跳转到登录页 login页面

    代码

    auth.guard.ts

    import { Injectable } from '@angular/core';
    import { CanActivate } from '@angular/router';
    import { Router } from '@angular/router'
       
    @Injectable({
      providedIn: 'root'
    })
    export class AuthGuard implements CanActivate {
      constructor(private router: Router) {}
       
      canActivate(): boolean {
        console.log('路由守卫')
        // 获取token
        const token = localStorage.getItem('login-token')
        if (!!token) {
          return true
        }
       
        // 跳转到登录页 login页面
        this.router.navigate(['/login'])
        return false
      }
    }
    

首页

① 结构layout

CSS中:host表示的是宿主元素

② 左侧内容Menu
③ 右侧内容AvatarPopconfirm

退出功能

① 基本实现
  1. 基本结构

    <a href="#" class="logout" (click)="logout($event)">
       <i class="anticon anticon-logout"></i>&nbsp;&nbsp;退出
    </a>
    
  2. 创建服务

$ ng g s home/home
  1. 在服务中导入HttpClent

  2. A标签被当做哈希值,进行了路由跳转

    A标签被当做哈希值,进行了路由跳转

    服务

    import { Injectable } from '@angular/core';
    import { HttpClient } from '@angular/common/http'
    import { URL } from '../config.js'
       
    @Injectable({
      providedIn: 'root'
    })
    export class HomeService {
       
      constructor(private http: HttpClient) { }
       
      logout() {
        // return this.http.delete(`${URL}/tokens`)
        return this.http.delete(`http://localhost:2080/tokens`)
      }
    }
    

    组件

    import { Component, OnInit } from '@angular/core';
    import { HomeService } from './home.service';
    .....
    export class HomeComponent implements OnInit {
      constructor(private homeService: HomeService) {}
       
      isCollapsed = false;
           
      logout() {
        this.homeService.logout().subscribe(
          res=> console.log('res', res)
        , (err) =>  {console.log('err', err )})
      }
       
      ngOnInit() { }
    }
    
    1. 增加e.preventDefault()来组织浏览器的默认行为

      报错500

    2. 根据文档增加请求头信息

      ...
      export class HomeService {
      ....
        logout() {
          const token = localStorage.getItem('login-token')
          return this.http.delete(`http://localhost:2080/tokens`, {
            headers: {
              Authorization: `Bearer ${token}`
            }
          })
        }
      }
            
      

      退出成功

    3. 退出之后需要调转到登录页,并且清除本地token

      .....
      import { Router } from '@angular/router'
      ...
      export class HomeComponent implements OnInit {
        constructor(private nzMessageService: NzMessageService,
          private homeService: HomeService,
          private router: Router ) {}
            
        isCollapsed = false;
            
        logout(e) {
          e.preventDefault()
          this.homeService.logout().subscribe(
            res => {
              console.log('退出成功了', res)
              // 先清除本地的token
              localStorage.removeItem('itcast-token')
              // 再跳转到登录页
              this.router.navigate(['/login'])
            },
            err => {
              console.log('退出失败,出错了:', err)
            }
          )
        }
            
        ngOnInit() { }
      }
      
    4. 给一个全局的提示Message

      ....
      import { NzMessageService } from 'ng-zorro-antd'
      .....
      export class HomeComponent implements OnInit {
        constructor(private nzMessageService: NzMessageService,
          private homeService: HomeService,
          private router: Router ) {}
        .....
        logout(e) {
          e.preventDefault()
                
          this.homeService.logout().subscribe(
            res => {
              console.log('退出成功了', res)
              // 先清除本地的token
              localStorage.removeItem('itcast-token')
              // 再跳转到登录页
              this.router.navigate(['/login'])
            },
            err => {
              console.log('退出失败,出错了:', err)
              this.nzMessageService.create('warning',`稍后再试`)
            }
          )
        }
            
        createMessage(type: string): void {
          this.message.create(type, `This is a message of ${type}`);
        }
            
        ngOnInit() { }
            
      }
      
② 确认退出增加用户体验
  1. 错误提示Message

  2. 防止误操作气泡确认框

    home.component.ts

    ....
    import { NzMessageService } from 'ng-zorro-antd'
    .....
    export class HomeComponent implements OnInit {
      constructor(private nzMessageService: NzMessageService,
        private homeService: HomeService,
        private router: Router ) {}
      .....
      logout(e) {
        // 气泡确认框会报错
        // e.preventDefault()
           
        this.homeService.logout().subscribe(
          res => {
            localStorage.removeItem('itcast-token')
            this.router.navigate(['/login'])
          },
          err => {
            console.log('退出失败,出错了:', err)
            // 错误提示
            this.nzMessageService.create('warning',`稍后再试`)
          }
        )
      }
      // 气泡确认框
      cancel(): void {
        this.nzMessageService.info('已取消');
      }
      ngOnInit() { }
       
    }
    

    home.component.html

    <div nz-col nzSpan="4">
      <nz-avatar nzIcon="user" nzSrc="//zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"></nz-avatar>
       
      <a nz-popconfirm nzTitle="是否确认退出?"  (nzOnConfirm)="logout()"  (nzOnCancel)="cancel()">&nbsp;&nbsp;退出</a>
    </div>
    
③ 退出功能样式调整
> 适当修改一下细节

异步路由

为什么不进入应用就加载所有功能模块?

  1. 增加了初始加载包的体积
  2. 降低了首屏加载速度
  3. 长时间白屏,用户体验差
  4. 对服务器造成额外的压力
特点

​ 用户请求某个模块的时候,才加载这个模块

优势

​ 减小了初始加载包体积,提高了首屏加载速度

使用
  1. 创建带有路由的模块

    $ ng g m employees --routing
    
  2. 创建员工列表组件:

    $ ng g c employees/employee-list
    
  3. 创建员工添加组件

    $ ng g c employees/employee-add
    
  4. 在 employees-routing 中配置路由规则

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
       
    import { EmployeeListComponent } from './employee-list/employee-list.component';
    import { EmployeeAddComponent } from './employee-add/employee-add.component';
       
    const routes: Routes = [
      { path: 'employee-list', component: EmployeeListComponent },
      { path: 'employee-add', component: EmployeeAddComponent },
    ];
       
    @NgModule({
      imports: [RouterModule.forChild(routes)],
      exports: [RouterModule]
    })
    export class EmployeesRoutingModule { }
    
  5. 在 app-routing 通过 loadChildren 异步加载employees模块

    异步加载的路由,实际上加载的是模块

    {
       path: 'employee',
       // 要将异步加载模块的路径以及模块名称配置在此处
       // 语法: 异步加载模块的路径#模块名称
       loadChildren: './employees/employees.module#EmployeesModule'
    }
    
    const appRoutes: Routes = [
      { path: 'home', 
        component: HomeComponent, 
        canActivate: [AuthGuard], 
        children: [
        	{ 
                path: 'employee', 
              	loadChildren: './employees/employees.module#EmployeesModule' 
            }
      ]},
      { path: 'login', component: LoginComponent }
    ]
    

注意:不要在根模块中加载 employees 模块

员工管理-列表

###### ① 结构

  1. UI样式使用NG-ZORRO的Table组件

报错以下内容就需要在employees.module.ts中导入一下NgZorroAntdModule

ERROR Error: Uncaught (in promise): Error: Template parse errors:
Can't bind to 'nzData' since it isn't a known property of 'nz-table'.
1. If 'nz-table' is an Angular component and it has 'nzData' input, then verify that it is part of this module.
2. If 'nz-table' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. ("<nz-table #basicTable [ERROR ->][nzData]="dataSet">
... 
  1. 模板引用变量用来获取DOM对象或者组件
<!-- #basicTable   和    #txt 是angular中的模板引用变量,用来获取DOM对象或者组件 -->
<input type="text" value="我是文本框的值" #txt> 
  1. 表格会进行数据的处理,最好使用basicTable.data
....
<tr *ngFor="let data of basicTable.data">
    <td></td>
    <td></td>
    <td></td>
    <td>
      <a>Action 一 </a>
      <nz-divider nzType="vertical"></nz-divider>
      <a>Delete</a>
    </td>
</tr>
.....
② 数据获取
  1. 创建服务
$ ng g s employees/employees
  1. 获取数据 ```TS import { Injectable } from ‘@angular/core’; import { HttpClient } from ‘@angular/common/http’ import { URL } from ‘../config’

@Injectable({ providedIn: ‘root’ }) export class EmployeesService {

constructor(private http: HttpClient) { }

// 获取数据 fetchData() { return this.http.get({URL}/employees) } }


3. 组件获取数据

```ts
....
import { EmployeesService } from '../employees.service'
....
export class EmployeeListComponent implements OnInit {

  constructor(private employeesService: EmployeesService) { }

  // 数据
  dataSet 
  
  ngOnInit() {
    this.employeesService.fetchData().subscribe(res => { console.log(res) })
  }
}

会报错

报错

显示没有权限。需要在服务里面添加一个请求头,就可以获取到数据

....
export class EmployeesService {
  constructor(private http: HttpClient) { }
  // 获取数据
  fetchData() {
    const token = localStorage.getItem('login-token')
    return this.http.get(`{URL}/employees`,{
      headers: {
        Authorization: `Bearer ${token}`
      }
    })
  }
}
  1. 在组件中使this.employeesList = res 就可以获取到数据
③ TS类型约束
  1. 新建employee.type.ts文件
export interface Employee {
  id: number
  name: string
  gender: string
  phoneNumber: string
  joinDate: number
}
  1. 在组件employee-list.component.ts中引用
....
import { Employee } from '../employee.type'

export class EmployeeListComponent implements OnInit {
....
  // 数据
  employeesList: Employee[] = []
  
  ngOnInit() {
    this.employeesService.fetchData()
        .subscribe((res: Employee[]) => { this.employeesList = res })
  }
}
  1. 封装获取数据的方法
.....
export class EmployeeListComponent implements OnInit {
  constructor(private employeesService: EmployeesService) { }
  // 数据
  employeesList: Employee[] = []
  // 封装获取数据的方法
  fetchData() {
    this.employeesService.fetchData()
        .subscribe((res: Employee[]) => { this.employeesList = res })
  }
  ngOnInit() { this.fetchData() }
}
④ angular管道/表格数据处理
  • 索引

  • 性别

  • 时间

    是angular中的管道,用来进行数据格式化
<nz-table #basicTable [nzData]="employeesList">
    <thead>...</thead>
    <tbody>
      <tr *ngFor="let data of basicTable.data; let i = index">
        <!--索引-->
        <td></td>
        <td></td>
        <!--性别-->
        <td></td>
        <td></td>
        <td></td>
        <!--时间-->
        <td></td>
        <td>...</td>
      </tr>
    </tbody>
  </nz-table>
⑤ 分页获取
  1. 在服务中添加参数
....
export class EmployeesService {
  constructor(private http: HttpClient) { }
  // 获取数据
  fetchData(curPage :number, pageSize) {
    const token = localStorage.getItem('login-token')
    const employeeUrl = `${URL}/employees?_page=${curPage}&_limit=${pageSize}`
    return this.http.get<Employee[]>(employeeUrl,{
      headers: {
        Authorization: `Bearer ${token}`
      }
    })
  }
}
  1. 在组件中传入参数
....
export class EmployeeListComponent implements OnInit {
  constructor(private employeesService: EmployeesService) { }
 ... 
  // 分页的数据
  curPage = 2
  pageSize = 3
  
  // 封装获取数据的方法
  fetchData() {
    // 传入参数
    this.employeesService.fetchData(this.curPage, this.pageSize)
    .subscribe((res: HttpResponse<Employee[]>) => { 
      console.log(res)
      this.employeesList = res.body
    })
  }
.... 
}

页面可以进行展示

  1. 在服务中获取其响应体
....
export class EmployeesService {
  constructor(private http: HttpClient) { }
  // 获取数据
  fetchData(curPage :number, pageSize) {
    const token = localStorage.getItem('login-token')
    const employeeUrl = `${URL}/employees?_page=${curPage}&_limit=${pageSize}`
    return this.http.get<Employee[]>(employeeUrl,{
      headers: {
        // 获取完整的响应体
        observe: 'response',
        Authorization: `Bearer ${token}`
      }
    })
  }
}
  1. 获取总条数

在控制台Networkhttp://localhost:2080/employees?_page=2&_limit=3里面获取到响应头

....
export class EmployeeListComponent implements OnInit {
....
  fetchData() {
    this.employeesService.fetchData(this.curPage, this.pageSize)
    .subscribe((res: HttpResponse<Employee[]>) => { 
      console.log(res.headers.get('X-Total-Count'))
      this.employeesList = res.body
    })
  }
.... 
}

在控制台可以得到一个是字符串5

在组件设置一个total

  1. 根据NG-ZORRO的表格控制API更改html格式,页面就可以进行分页的展示。
<nz-table #basicTable [nzData]="employeesList" [nzFrontPagination]="false" [nzTotal]="total" [(nzPageIndex)]="curPage" [nzPageSize]="pageSize" (nzPageIndexChange)="fetchData()">
    <thead>...</thead>
    <tbody>...</tbody>
  </nz-table>
⑥ TS类型约束

员工管理-删除

  1. UI样式:气泡确认框

    employee-list.component.html

    .... 
    <a nz-popconfirm nzTitle="确定要删除吗?" nzPlacement="bottom" (nzOnConfirm)="handleDelete(data.id)"  (nzOnCancel)="handleDelcancel()">删除</a>
    ...
    

    employee-list.component.ts

    import { NzMessageService } from 'ng-zorro-antd';  
    // 删除
    ...
      handleDelete(): void {
        this.nzMessageService.info('已删除');
      }
       
      handleDelcancel(): void {
        this.nzMessageService.info('取消删除');
      }
    ...
    
  2. handleDelete传一个删除的id,控制台可显示出id,可与Network接口中的id相对应

    employee-list.component.ts

      // 删除
      handleDelete(id: number): void {
        this.nzMessageService.info('已删除');
       
        this.employeesService.delEmployee(id)
            .subscribe(res =>{
             //  方法一  过滤一下
              this.employeesList = this.employeesList.filter(
                employee => employee.id !== id
              )
               
             // 方法二   重新加载
             // this.fetchData()
            })
      }
       
      handleDelcancel(): void {
        this.nzMessageService.info('取消删除');
      }
    

    employees.service.ts

    // 删除数据
      delEmployee(id: number) {
        const token = localStorage.getItem('login-token')
        return this.http.delete(`${URL}/employees/${id}`, {
          headers: {
            Authorization: `Bearer ${token}`
          }
        })
      }
    

HTTP拦截器

① 了解
  1. 问题:每个请求都要在请求头中添加 Authorization,太繁琐

  2. 解决方式:使用 HttpClient 拦截器

  3. 作用:用它们监视和转换从应用发送到服务器的 HTTP 请求

② 使用
  1. 手动创建拦截器服务 app/auth-interceptors/auth.interceptor.ts

  2. 继承接口 HttpInterceptor 并实现 intercept() 方法

    1. 参数一:**req: HttpRequest** 请求头对象
    2. 参数二:next: HttpHandler 下一个拦截器,如果只有一个,则会把请求发给服务器,并接收服务器的响应
    3. req的属性是只读(readonly)

    修改请求方式

    1. 先克隆
    2. 修改这个克隆体
    3. 然后把克隆体传给 next.handle()

app/auth-interceptors/auth.interceptor.ts

import { Injectable } from '@angular/core'
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'
import { Observable } from 'rxjs'

@Injectable() 

export class AuthInterceptors implements HttpInterceptor{

  // 拦截使用 HttpClient 方法的请求
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('http  拦截器')
    return next.handle(req)
  }
}

app/app.module.ts

.....
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptors } from './auth-interceptors/auth.interceptor'
....
@NgModule({
  // providers: [{ provide: NZ_I18N, useValue: zh_CN }],
  providers: [
    { provide: NZ_I18N, useValue: zh_CN },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptors,
      multi: true
    }
  ]..
})

添加一下token ,headers

export class AuthInterceptors implements HttpInterceptor{
  // 拦截使用 HttpClient 方法的请求
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      
    const token = localStorage.getItem('login-token')
    // 统一添加请求头
    const anthReq = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${token}`)
    })
    console.log('http  拦截器')
    return next.handle(anthReq)
  }
}

就可以把之前在home,login服务中添加的token ,headers删除掉了,在测试页面看看是否可以

让登陆页面不走这个请求头

login.service.ts中添加一个自定义的请求头

export class LoginService {

  constructor(private http: HttpClient) { }

  login(loginForm: LoginForm) {

    return this.http.post(`${URL}/tokens`, loginForm, {
      headers: {
        'No-Auth': 'TRUE'
      }
    })
  }
}

auth.interceptor.ts中添加判断条件

.....
export class AuthInterceptors implements HttpInterceptor{

  // 拦截使用 HttpClient 方法的请求
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 如果是登录,不需要添加 Authorization
    if(req.headers.get('No-Auth')==='TRUE') { return next.handle(req)}
    // 非登录请求,都要添加 Authorization
    const token = localStorage.getItem('login-token')
    // 统一添加请求头
    const anthReq = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${token}`)
    })
    console.log('http  拦截器')
    return next.handle(anthReq)
  }
}
③ 处理错误信息
// 管道
import { tap } from 'rxjs/operators'
...
export class AuthInterceptors implements HttpInterceptor{
  constructor(private router: Router) { }

  // 拦截使用 HttpClient 方法的请求
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
..........
    return next.handle(anthReq).pipe(
      tap(
        // 成功的回调
        ok => {},
        // 失败的回调
        error => {
          console.log('捕捉到错误', error)
          // 遇到401报错,移出token,并且跳转到登陆页面
          if (error.status === 401) {
            localStorage.removeItem('login-token')
            this.router.navigate(['/login'])
          }
        }
      )
    )
  }
}

员工管理-添加

① 结构
  1. UI样式使用NG-ZORRO的FORM单选框日期选择框组件
② 添加数据及表单验证
  1. 会进行报错,要先记得在 employees.module.ts中导入ReactiveFormsModule

    模块之间不能共享指令等

   ERROR Error: Uncaught (in promise): Error: Template parse errors:
   No provider for ControlContainer ("[ERROR ->]<form nz-form (ngSubmit)="submitForm($event,validateForm.value)">
       <!-- usernam -->
       <nz-form-i"): ng:///EmployeesModule/EmployeeAddComponent.html@0:0
   Error: Template parse errors:
   No provider for ControlContainer ("[ERROR ->]<form nz-form (ngSubmit)="submitForm($event,validateForm.value)">
       <!-- usernam -->
       .....
import { ReactiveFormsModule } from '@angular/forms'
...

@NgModule({
  imports: [
    CommonModule,
    EmployeesRoutingModule,
    NgZorroAntdModule,
    ReactiveFormsModule
  ],

  declarations: [EmployeeListComponent, EmployeeAddComponent]
})
export class EmployeesModule { }
  1. 更改数据,在employee-add.component.ts中设置初始数据,在employee-add.component.html进行相应的改变
....
export class EmployeeAddComponent implements OnInit {
  constructor(private fb: FormBuilder) {
    this.employeeAddForm = this.fb.group({
      name: [''],
      gender: ['0'],
      email: [''],
      phoneNumber: [''],
      joinDate: ['']
    });
  }
   .....
}
  1. 添加表单校验
  1. name: 必填,长度不能少于2
  2. gender: 必填,互斥(在html已体现)
  3. email: 必填,email格式
  4. phoneNumber: 匹配手机号码格式
  5. joinDate: 日期格式,入职日期不能早于今天
....
// 手机号码的正则
const PHONE_NUMBER_REGEXP = /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$/
....
export class EmployeeAddComponent implements OnInit {
  constructor(private fb: FormBuilder) {
    this.employeeAddForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      gender: ['0'],
      email: ['',[Validators.required, Validators.email]],
      phoneNumber: ['', [PHONE_NUMBER_REGEXP]],
      joinDate: ['', [this.joinDateValidate]]
    });
  }
  // 日期的自定义校验规则
  joinDateValidate(control: FormControl) {
    const selectDate = +control.value
    const curDate = +new Date()

    if (selectDate > curDate) {
      return { date: true }
    }
    return null
  }
   .....
}

进行整理,让代码更加合理化

....
// 手机号码的正则
const PHONE_NUMBER_REGEXP = /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$/
....
export class EmployeeAddComponent implements OnInit {
  constructor(private fb: FormBuilder) {  }
    
  employeeAddForm: FormGroup;

  ngOnInit() {
    this.employeeAddForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      gender: ['0'],
      email: ['',[Validators.required, Validators.email]],
      phoneNumber: ['', [Validators.pattern(PHONE_NUMBER_REGEXP)]],
      joinDate: ['', [this.joinDateValidate]]
    });
  }

  // 日期的自定义校验规则
  joinDateValidate(control: FormControl) {
    const selectDate = +control.value
    // const selectDate = control.value
    // console.log(selectDate)
    const curDate = +new Date()
    if (selectDate > curDate) { return { date: true } }
    return null
  }
   .....
}
③ 按钮功能
  1. 按钮–添加/重置

employee-add.component.html

   <nz-form-item>
      <nz-form-control [nzOffset]="7" [nzSpan]="12">
        <button nz-button nzType="primary" [disabled]="!employeeAddForm.valid">添加</button>
        <button nz-button (click)="resetForm($event)">重置</button>
      </nz-form-control>
    </nz-form-item>

employee-add.component.ts

....
export class EmployeeAddComponent implements OnInit {
   .....
  submitForm = ($event, value) => {
    $event.preventDefault();
    for (const key in this.employeeAddForm.controls) {
      this.employeeAddForm.controls[ key ].markAsDirty();
      this.employeeAddForm.controls[ key ].updateValueAndValidity();
    }
    console.log(value);
  };

  resetForm(e: MouseEvent): void {
    e.preventDefault()
    const employeeAddForm = this.employeeAddForm
    const { controls } = employeeAddForm
	// 添加一个 gender的默认值   将之前的for删除
    this.employeeAddForm.reset({
      gender: '0'
    });
    for (const key in controls) {
      controls[ key ].markAsPristine();
      controls[ key ].updateValueAndValidity();
    }

    if(!employeeAddForm.valid){ return }
  }
}
  1. 添加功能

employee-add.component.ts中,发起请求,添加员工

....
export class EmployeeAddComponent implements OnInit {
   .....
  submitForm = ($event, value) => {
    $event.preventDefault();
    for (const key in this.employeeAddForm.controls) {
      this.employeeAddForm.controls[ key ].markAsDirty();
      this.employeeAddForm.controls[ key ].updateValueAndValidity();
    }
    if(!employeeAddForm.valid){ return }
    // console.log(value);
      
    // 发起请求,添加员工
    let { joinDate } = employeeAddForm.value
    if(!joinDate) { joinDate = +new Date() }
    const params = {...employeeAddForm.value, joinDate: +joinDate}
    
    console.log(params)
  };
}

获取到params

employees.service.ts服务中,

.....  
// 添加数据
  addEmployee(employee: Employee) {
    return this.http.post(`${URL}/employees`, employee)
  }
....

employee-add.component.ts中添加服务。

....
export class EmployeeAddComponent implements OnInit {
   .....
  submitForm = ($event, value) => {
    .....
    // 发起请求,添加员工
    let { joinDate } = employeeAddForm.value
    if(!joinDate) { joinDate = +new Date() }
    const params = {...employeeAddForm.value, joinDate: +joinDate}
    
    // console.log(params)

    this.employeesService.addEmployee(params)
        .subscribe(res => {
          console.log(res)
          // 提示消息
          this.nzMessageService.create('success', '添加员工成功')
          // 重新加载表格 
          this.resetEmployeeForm()
          // 跳转到list页面
          this.router.navigate(['/home/employee'])
        })
  };

 // 更改为重新加载EmployeeForm
 resetEmployeeForm(): void {

    const employeeAddForm = this.employeeAddForm
    const { controls } = employeeAddForm

    this.employeeAddForm.reset({
      gender: '0'
    });
    for (const key in controls) {
      controls[ key ].markAsPristine();
      controls[ key ].updateValueAndValidity();
    }
  }
 // 添加参数, 阻止默认行为
  resetForm(e: MouseEvent): void {
    e.preventDefault()
    this.resetEmployeeForm()
  }
}

员工管理-编辑

① 结构
  1. UI样式使用NG-ZORRO的对话框组件

employee-list.component.html

<nz-table >
    <thead>.....</thead>
    <tbody>
      <tr>
		....
        <td>
          <a (click)="showEditEmployeeModal()"><span>修改</span></a>
			....
        </td>
      </tr>
    </tbody>
  </nz-table>

  <!-- 修改的样式 -->
  <nz-modal [(nzVisible)]="isShowEmployeeModal" nzTitle="编辑员工" (nzOnCancel)="handleEditEmployeeCancel()" (nzOnOk)="editEmployee()">
    <p>第一行</p>
  </nz-modal>

employee-list.component.ts

 ...
 // 修改
  isVisible = false;

  showEditEmployeeModal(): void {
    this.isVisible = true;
  }

  // 确定修改
  handleEditEmployeeOK(): void {
    console.log('确定修改');
    this.isVisible = false;
  }

  // 取消修改
  handleEditEmployeeCancel(): void {
    console.log('取消修改');
    this.isVisible = false;
  }
  1. 将添加的表单复制过来

employee-list.component.html

<nz-table >
    <thead>.....</thead>
    <tbody>
      <tr>
		....
        <td>
          <a (click)="showEditEmployeeModal()"><span>修改</span></a>
			....
        </td>
      </tr>
    </tbody>
  </nz-table>
 <!-- 修改的样式 -->
  <nz-modal [(nzVisible)]="isShowEmployeeModal" nzTitle="编辑员工" (nzOnCancel)="handleEditEmployeeCancel()" (nzOnOk)="editEmployee()">
      <form nz-form [formGroup]="employeeEditForm">
        <!-- usernam -->
        <nz-form-item>
          <nz-form-label [nzSpan]="7" nzRequired>姓名</nz-form-label>
          <nz-form-control [nzSpan]="12" nzHasFeedback>
            <input nz-input formControlName="name" placeholder="请输入姓名">
            <nz-form-explain *ngIf="employeeEditForm.get('name').dirty && employeeEditForm.get('name').errors">
              <ng-container *ngIf="employeeEditForm.get('name').hasError('required')">
                请填写姓名
              </ng-container>
              <ng-container *ngIf="employeeEditForm.get('name').hasError('minlength')">
                姓名长度最少为2位
              </ng-container>
            </nz-form-explain>
          </nz-form-control>
        </nz-form-item>
        <!-- gender -->
        <nz-form-item>
            <nz-form-label [nzSpan]="7">性别</nz-form-label>
            <nz-form-control [nzSpan]="12"> 
                <nz-radio-group formControlName="gender">
                  <label nz-radio nzValue="0"></label>
                  <label nz-radio nzValue="1"></label>
                </nz-radio-group>
            </nz-form-control>
          </nz-form-item>
        <!-- email -->
        <nz-form-item>
          <nz-form-label [nzSpan]="7" nzRequired>E-mail</nz-form-label>
          <nz-form-control [nzSpan]="12" nzHasFeedback>
            <input nz-input formControlName="email" placeholder="请输入邮箱" type="email">
            <nz-form-explain *ngIf="employeeEditForm.get('email').dirty&&employeeEditForm.get('email').errors">
              <ng-container *ngIf="employeeEditForm.get('email').hasError('email')">
                E-mail输入格式不正确
              </ng-container>
              <ng-container *ngIf="employeeEditForm.get('email').hasError('required')">
                请输入E-mail
              </ng-container>
            </nz-form-explain>
          </nz-form-control>
        </nz-form-item>
        <!-- phone -->
        <nz-form-item>
          <nz-form-label [nzSpan]="7">手机号码</nz-form-label>
          <div>
            <nz-form-control [nzSpan]="12" nzHasFeedback>
              <input nz-input placeholder="请输入手机号码" formControlName="phoneNumber">
              <nz-form-explain *ngIf="employeeEditForm.get('phoneNumber').dirty&&employeeEditForm.get('phoneNumber').hasError('pattern')">手机号码格式不正确</nz-form-explain>
            </nz-form-control>
          </div>
        </nz-form-item>
        <!-- joinDate -->
        <nz-form-item>
          <nz-form-label [nzSm]="7" [nzXs]="24">入职时间</nz-form-label>
          <nz-form-control [nzSm]="16" [nzXs]="24">
            <nz-date-picker formControlName="joinDate"></nz-date-picker>
            <nz-form-explain *ngIf="employeeEditForm.get('joinDate').dirty&&employeeEditForm.get('joinDate').errors">
              <ng-container *ngIf="employeeEditForm.get('joinDate').hasError('date')">
                入职时间不能早于今天
              </ng-container>
            </nz-form-explain>
          </nz-form-control>
        </nz-form-item>
        <!-- add   button -->
        <nz-form-item>
          <nz-form-control [nzOffset]="7" [nzSpan]="12">
            <button nz-button nzType="primary" [disabled]="!employeeEditForm.valid">添加</button>
          </nz-form-control>
        </nz-form-item>
      </form>
  </nz-modal>

employee-list.component.ts

.... 
import {
  FormBuilder,
  FormControl,
  FormGroup,
  ValidationErrors,
  Validators
} from '@angular/forms'

// 手机号码的正则
const PHONE_NUMBER_REGEXP = /^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$/
.....
export class EmployeeListComponent implements OnInit {

  constructor(private employeesService: EmployeesService,
    private nzMessageService: NzMessageService,
    private fb: FormBuilder) { }

  ngOnInit() {
    this.fetchData()
    this.employeeEditForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      gender: ['0'],
      email: ['',[Validators.required, Validators.email]],
      phoneNumber: ['', [Validators.pattern(PHONE_NUMBER_REGEXP)]],
      joinDate: ['', [this.joinDateValidate]]
    });
  }

  // 日期的自定义校验规则
  joinDateValidate(control: FormControl) {
    const selectDate = +control.value
    // const selectDate = control.value
    // console.log(selectDate)
    const curDate = +new Date()
    if (selectDate > curDate) { return { date: true } }
    return null
  }

  // 修改
  isShowEmployeeModal = true
  employeeEditForm: FormGroup;

  showEditEmployeeModal(): void {
    this.isShowEmployeeModal = true;
  }

  // 确定修改
  editEmployee(): void {
    console.log('确定修改');
    this.isShowEmployeeModal = false;
  }

  // 取消修改
  handleEditEmployeeCancel(): void {
    console.log('取消修改');
    this.isShowEmployeeModal = false;
  }
}
② 展示员工数据
  1. 需要拿到需要修改数据的ID
  2. 根据ID获取员工的数据
  3. employees.service.ts服务中
  // 根据id查询员工信息
  getEmployeeById(id: number) {
    return this.http.get<Employee>(`${URL}/employees/${id}`)
  }
  1. employee-list.component.html中的showEditEmployeeModal方法中添加参数ID
<a (click)="showEditEmployeeModal(data.id)"><span>修改</span></a>
  1. employee-list.component.ts中的showEditEmployeeModal方法中去根据ID获取数据
  showEditEmployeeModal(id: number): void {
    this.isShowEmployeeModal = true;
    // 根据id获取员工的数据
    this.employeesService.getEmployeeById(id)
        .subscribe((employee: Employee) => {
          console.log(employee)
        })
  }

  1. 将获取到的数据展示于表单中,发现由于日期没有显示出来,会进行报错(时间的类型不一样)

  showEditEmployeeModal(id: number): void {
    this.isShowEmployeeModal = true;
    // 根据id获取员工的数据
    this.employeesService.getEmployeeById(id)
        .subscribe((employee: Employee) => {
          // 将获取到的数据展示于表单中
          this.employeeEditForm.patchValue(employee)
        })
  }

  1. 将日期进行类型转换
  showEditEmployeeModal(id: number): void {
    this.isShowEmployeeModal = true;
    // 根据id获取员工的数据
    this.employeesService.getEmployeeById(id)
        .subscribe((employee: Employee) => {
          // console.log(employee)
          // 将获取到的数据展示于表单中
          const { joinDate } = employee
          this.employeeEditForm.patchValue({
            ...employee,
            joinDate: new Date(joinDate)
          })
        })
  }
③ 更新员工数据
  1. 先进行数据的验证

  // 确定修改
  editEmployee(): void {
    // console.log('确定修改');
    this.isShowEmployeeModal = false;
    const employeeEditForm =  this.employeeEditForm
    const { controls } = employeeEditForm

    for (const key in controls) {
      controls[ key ].markAsDirty();
      controls[ key ].updateValueAndValidity();
    }

    console.log(employeeEditForm.valid)

    if(!employeeEditForm.valid) { return }

  }
  1. employees.service.ts服务中
  // 根据员工id更新员工
  updateEmployeeById(id: number, params: Employee) {
    return this.http.patch<Employee>(${URL}/employees/${id}, params)
  }
  1. employee-list.component.html中的editEmployee方法中发起请求,更新员工信息,可以在控制台打印出编辑
  // 确定修改
  editEmployee(): void {
    ....
    let { joinDate } = employeeEditForm.value
    if(!joinDate) { joinDate = +new Date() }
    const params = { ...employeeEditForm.value, joinDate: +joinDate}

    this.employeesService.updateEmployeeById(this.editEmployeeId, params)
        .subscribe(res => {
          console.log('编辑成功', res)
        })
  }

editEmployee方法中是拿不到ID的, 需要在showEditEmployeeModal方法中获取ID的时候进行一些保存 。

  ... 
  editEmployeeId: number
  ...
  showEditEmployeeModal(id: number): void {
    this.editEmployeeId = id
    ....
  }

  1. 使其编辑项在列表展示出来
 // 确定修改
  editEmployee(): void {
   ......
    this.employeesService.updateEmployeeById(this.editEmployeeId, params)
        .subscribe((res: Employee) => {
          console.log('编辑成功', res)
          //  将关闭窗口的功能移下来
          this.isShowEmployeeModal = false
        
          const index = this.employeesList.findIndex(employee => employee.id === res.id)
          this.employeesList[index] = res 
        })

  }
④ 关闭编辑窗口数据重置

把添加的重置取过来。在editEmployeehandleEditEmployeeCancel方法中调用一下

 resetEmployee() {
    const employeeEditForm = this.employeeEditForm
    const { controls } = employeeEditForm

    this.employeeEditForm.reset()
    Object.keys(controls).forEach(key => {
      controls[key].markAsPristine()
      controls[key].updateValueAndValidity()
    })
  }

本文首次发布于 Azr的博客, 作者 @azrrrrr ,转载请保留原文链接.

原文链接: http://amor9.cn/2019/02/02/ng7/