向全栈迈进——Angular+Tornado开发树洞博客(四)

在上一篇博客中,我们实现了第一个angular组件,并把它作为了我们的主页面。在这期博客中,我们将实现用户注册功能的前端以及后端功能的实现,真正进入到全栈开发。

八 用户注册功能的开发

这个功能的实现分为两部分:前端部分和后端部分。前端部分包括angular组件的建立以及服务的建立,而后端部分为tornado服务器部分以及对应的数据库表的建立。我们之后的每个功能都会如下分别介绍前端和后端部分。
我们的这个register组件是一个让用户输入各种注册信息的表单。因此,这里有必要介绍一下angular中的表单。

1 angular中表单的介绍

angular提供了两大类表单:响应式表单和模板驱动型表单。响应式表单在后台是依靠表单类来建立的,表单中的每个元素都对应表单类对象的一个成员,类似Django提供的表单;与模板驱动型表单相比,响应式表单更加稳定,且在可复用性上更胜一筹。模板驱动型表单并不是通过类来实现的,而是需要用户自己去处理每个元素所对应的变量。模板驱动型表单不可复用,适用于简单场景。
下面让我们看一下这两种表单在数据绑定上的不同。响应式表单将整个表单视为一个FormGroup对象,将每个表单中的元素视为一个FormControl对象,一个FormGroup对象可以包括多个FormControl对象,这样就简单地将表单和其背后的模型建立了联系。每当我们操作前端表单元素的时候,值的变化会同步更新到其所对应的FormControl对象中;反之,如果我们在后台中修改了FormControl的值,那么对应表单元素的值也会随之改变,如图所示:
响应式表单
而对于模板驱动型表单,我们并没有一个FormGroup类来管理整个表单,我们需要通过ngModel来隐式为每个表单元素来建立FormControl,并且不能直接访问这个隐式的FormControl对象:
模板驱动型表单
通过这两种表单,都可以让我们的前端输入的数据同步到后面的模型中。个人感觉,响应式表单处理起来更加方便,因此这个系列的博客大多采用响应式表单。

2 前端部分

2.1 注册组件的建立

我们打开cmd页面,输入如下命令建立register组件:

ng g c register

稍等片刻,ng工具就会生成register组件。
我们要使用NG-ZORRO提供的表单样式,以及使用angular提供的表单模块。因此,我们在app.module.ts里引入这些模块:

//app.module.ts
//其他模块
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzFormModule } from 'ng-zorro-antd/form';
import { NzInputModule } from 'ng-zorro-antd/input';
import { NzMessageModule } from 'ng-zorro-antd/message';
//其他模块

@NgModule({
  declarations: [
    AppComponent,
    RegisterComponent,
    MainlayoutComponent,
  ],
  imports: [
	//其他模块
    FormsModule,
    ReactiveFormsModule,
    NzFormModule,
    NzInputModule,
    NzButtonModule,
    NzMessageModule,
    //其他模块
  ],
  providers: [CookieService],
  bootstrap: [AppComponent]
})
export class AppModule { }

这里为了篇幅起见,将其他模块隐去。我们现在引入了这些模块:

  • FormsModule:angular提供的模板驱动型表单模块,包括一些基础的表单功能。
  • ReactiveFormsModule:angular提供的响应式表单模块,是我们这个系列博客中主要使用的表单类型。
  • NzButtonModule:NG-ZORRO的按钮模块,提供一些好看的按钮样式。
  • NzFormModule:NG-ZORRO的表单模块,需要依赖angular的表单模块,也是提供一些自己的控制逻辑以及样式。
  • NzInputModule:NG-ZORRO的输入域模块,提供各种input标签。
  • NzMessageModule:NG-ZORRO的消息模块,提供各种弹出信息以及弹出提示。

我们打开register.component.html,开始编写html代码:

<!--register.component.html-->
<form nz-form [formGroup]="registerForm" (ngSubmit)="onSubmit()">

  <nz-form-item>
    <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="username" nzRequired>用户名</nz-form-label>
    <nz-form-control [nzSm]="14" [nzXs]="24">
      <input
        nz-input
        type="text"
        id="username"
        formControlName="username"
      />
    </nz-form-control>
  </nz-form-item>
  <nz-form-item>
    <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="password" nzRequired>密码</nz-form-label>
    <nz-form-control [nzSm]="14" [nzXs]="24">
      <input
        nz-input
        type="password"
        id="password"
        formControlName="password"
      />
    </nz-form-control>
  </nz-form-item>
  <nz-form-item>
    <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="checkpassword" nzRequired>确认密码</nz-form-label>
    <nz-form-control [nzSm]="14" [nzXs]="24">
      <input nz-input type="password" formControlName="checkpassword" id="checkpassword" />
    </nz-form-control>
  </nz-form-item>
  <nz-form-item>
    <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="email">E-mail</nz-form-label>
    <nz-form-control [nzSm]="14" [nzXs]="24">
      <input nz-input formControlName="email" id="email" />
    </nz-form-control>
  </nz-form-item>
  <nz-form-item nz-row style="margin-bottom:8px;">
    <nz-form-control [nzSpan]="14" [nzOffset]="6">
      <button nz-button nzType="primary">注册</button>
    </nz-form-control>
  </nz-form-item>
</form>

在这里我们建立了一个名为registerForm的响应式表单,包含以下元素:

  1. username,用户名
  2. password,密码
  3. checkpassword,确认密码
  4. email,电子邮件
  5. 一个注册按钮

以上这些名称均为后端的FormControl对象名称,而registerForm为后端的FormGroup对象名称。(ngSubmit)表明当我们提交表单时,要调用哪个函数,这里我们提交表单时调用的是onSubmit函数。
我们打开register.component.css文件,加入以下代码:

[nz-form] {
  max-width: 600px;
}

再打开register.component.ts文件,开始编写html背后的逻辑部分:

//register.component.ts
import { Component, OnInit } from '@angular/core';
import { FormControl,FormGroup } from '@angular/forms';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
  registerForm = new FormGroup({
    username:new FormControl(''),
    password:new FormControl(''),
    checkpassword:new FormControl(''),
    email:new FormControl('')
  });
  constructor() {
    
   }

  onSubmit(){
    console.log(this.registerForm.value)
  }

  ngOnInit() {
  }

}

在这个文件里,我们引入了FormControl和FormGroup两个类,并定义了一个名为registerForm的FormGroup对象,其中包含4个FormControl对象,分别名为username, password, checkpassword和email。注意,这几个对象的名称要和html中的[formGroup]还有formControlName一一对应,否则编译会报错。
然后,我们又实现了一个简单的onSubmit函数,功能为在控制台中印出通过表单得到的值。
这样,我们就建立好了前端的页面,该为它添加一个路由了,以便我们能从主页访问到这个页面。
我们打开app-routing.module.ts,在routes数组中加入以下一行:

//app-routing.module.ts
//...
const routes: Routes = [
  {path:'register',component:RegisterComponent},
];
//...

然后打开我们的主页面组件的mainlayout.component.html文件,为菜单栏中的注册菜单项添加路由:

<!--src\app\mainlayout\mainlayout.component.html-->
<!--...-->
<!--两个注册的地方都要加-->
<li nz-menu-item routerLink='/register'>注册</li>
<!--...-->

在angular中,所谓的路由都是指在不同的组件间路由,而不是传统中的在不同html页面中路由。angular提供了routerLink标签来指定组件在angular的url,这也和传统html中a标签的href属性有区别。因此,这行代码的含义为当点击注册菜单项时,会访问到/register这个url,并根据path中的设定在下面的<router-outlet></router-outlet>标签位置显示我们刚实现的register组件。
下面让我们保存所有修改的文件,输入npm start。待项目跑起来后,点击菜单栏上的注册,会发现弹出了我们的注册表单:
注册表单
让我们输入一点东西,打开网页控制台,点击注册按钮,会看到我们输入的东西在控制台中显示出来了,表明已经触发了onSubmit函数:
在这里插入图片描述

2.2 注册服务的建立

我们已经实现了注册组件的html部分,以及实现了一个假的onSubmit函数来显示我们输入到表单的值。现在我们来建立一个服务,从而让表单数据可以真正提交到我们的tornado server上。
服务(service)是angular中的一个重要概念。angular采用依赖注入的设计模式,所以我们的组件不直接负责与后台server交互,而是将若干个service作为自身的成员变量,让这些service去实现与后台server交互的方法,从而实现组件与后台server的通信。这样的好处是,只需改动service的代码,就可以修改组件的行为,而无需动到组件本身的代码。
Service具备和Backend server通信的能力,那么让Component拥有一个service的成员变量,就可以使Component也具备和后端通信的能力
Service1和Service2具备同不同server通信的能力,通过更换service,可以使Component和不同的server进行通信

我们打开cmd,输入以下命令,建立一个service:

ng g s service/register

稍等片刻,ng工具会为我们建立service目录,并在底下建立register.service.ts文件,这便是我们建立的service了。
这个service我们主要用于访问tornado的server,因此我们需要在app.module.ts中引入HttpClientModule模块:

//app.module.ts
import { HttpClientModule } from '@angular/common/http';

顾名思义,这个模块里的组件可以让我们通过http方式访问服务器。
现在,让我们在register目录下建立一个名为registerUser.ts的文件,并输入以下内容:

//src\app\register\registerUser.ts
export interface registerUserData {
    username: string;
    password: string;
    checkpassword:string;
    email:string;
  }

我们在这个文件里建立了一个名为registerUserData的接口。angular的接口虽然也叫interface,但它是一种数据类型,而不是抽象类。interface规定了前端要给后端传递什么格式的数据,本质是一种指定了key和value的json数据。
让我们回到register.service.ts文件,编写以下内容:

//src\app\service\register.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { registerUserData } from '../register/registerUser';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class RegisterService {

  constructor(private http:HttpClient) { }

  registerUser(userData:registerUserData):Observable<registerUserData>{
    return this.http.post<registerUserData>('http://localhost:8000/register',userData);
  }
}

我们在这个service里定义了一个HttpClient对象,这使得我们的service有能力去给后端的server发送http请求;接下来我们实现了registerUser对象,其接受registerUserData类型的数据,并以post的方式发送到我们的server地址中。
这样,我们的service就写完了,让我们把它放到register组件中去。打开register.component.ts文件,修改如下:

//src\app\register\register.component.ts
import { Component, OnInit } from '@angular/core';
import { FormControl,FormGroup } from '@angular/forms';

import { RegisterService } from '../service/register.service';
import { registerUserData } from './registerUser';
import { Router } from '@angular/router';
import { NzMessageService } from 'ng-zorro-antd/message';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
  registerForm = new FormGroup({
    username:new FormControl(''),
    password:new FormControl(''),
    checkpassword:new FormControl(''),
    email:new FormControl('')
  });
  newUser:registerUserData;
  constructor(private registerService:RegisterService,private route:Router,private message: NzMessageService) {
    this.newUser = {username:'',password:'',checkpassword:'',email:''}
   }

  onSubmit(){
    console.log(this.registerForm.value)
    this.registerUser()
  }

  registerUser():void{
    this.newUser = this.registerForm.value;
    if (this.newUser.password != this.newUser.checkpassword)
    {
      this.message.error('两次输入的密码不一致')
    }
    else
    {
      this.registerService.registerUser(this.newUser).subscribe((data:any) => 
      {
        if (data['result'] == 'Success')
        {
          this.registerForm.setValue({
          username:'',
          password:'',
          checkpassword:'',
          email:''
          });
          //跳转到首页
          this.route.navigateByUrl('')
        }
        else
        {
          console.log(data);
          this.message.error('注册失败');
        } 
      }
      )
    }
  }

  ngOnInit() {
  }

}

我们这次引入了RegisterService类、registerUserData接口以及Router和NzMessageService类。Router用于稍后的重定向页面,而NzMessageService类用于弹出提示信息。我们声明了一个registerUserData类型的新对象newUser,用于接收表单的数据;注意到我们修改了组件的构造函数,声明了RegisterService,Router和NzMessageService类的对象各一个,并且在构造函数中初始化了newUser(这里是因为我们开启了严格模式的原因,如果不开启严格模式的话,这里可以不初始化newUser)。
然后,我们开始实现核心函数——registerUser。这个函数的主要目的就是来调用我们的registerService,通过service将表单数据发送给后台server并得到返回值。由于我们这里使用响应式表单,所以我们可以直接通过this.newUser=this.registerForm.value将表单的值赋给newUser对象。然后我们这里做一个简单的验证:如果确认密码和密码两项输入的内容不一样,就弹出错误信息,如果一样的话,就调用service提供的registerUser对象。
调用registerUser对象背后过程比较复杂,限于篇幅将把背后过程放在后端篇介绍,这里只介绍含义。这里的含义是指,前端通过调用registerUser对象,将newUser作为表单数据传递给了后台的server,随后得到了服务器返回的数据data。这里的data也是json格式的数据,其仅包含一个key:result。因此,如果data的result是Success的话,我们就会清空表单数据,并将其跳转到首页;如果data的result不是Success的话,我们会调用NzMessageService去弹出一个错误信息。
最后,我们在onSubmit函数中调用这个新写的registerUser函数。
在这期博客中,我们实现了用户注册功能的全部前端部分的代码,实现了我们的第一个表单和第一个服务。限于篇幅所限,我们将在下一篇博客中实现这个功能的后端部分,以及讲解registerUser对象调用的背后的逻辑,希望大家继续关注~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值