在上一篇博客中,我们完成了用户注册功能的开发,实现了第一个完整的功能。在这篇博客中,我们将介绍angular通过服务访问后端服务器的原理,以及开始开发用户登录功能。
九 angular中的Service原理
1 可观察对象Observable
首先,让我们复习一下我们的服务是怎么写的:
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);
}
}
我们先从import看起。HttpClient顾名思义,是用于访问http服务器的客户端;registerUserData是我们定义的数据类型,告诉我们要怎么给服务器传递数据;而Observable是从RxJS中引入的概念,可直接翻译为可观察对象。 在继续介绍这个Observable之前,我们先要介绍一下数据的拉取(Pull)和推送(Push)模型。在这两个模型中,都定义了生产者和消费者的角色,生产者负责生产数据,而消费者会使用生产者生产的数据。对于http请求而言,生产者的“生产”就是向后端服务器发送请求,并得到服务器返回的数据,即服务器返回的数据就是生产者造出来的“产品”;而消费者会使用生产者拿到的服务器的数据,将其显示在页面上。 我们可以用这样一个表格来描述生产者和消费者在拉取和推送模型下的行为,此表格位于RxJs官方文档:
| 生产者 | 消费者 |
---|
拉取 | 被动的: 当被请求时产生数据。 | 主动的: 决定何时请求数据。 | 推送 | 主动的: 按自己的节奏产生数据。 | 被动的: 对收到的数据做出反应。 |
通过以上的表格可以看出,在拉取过程中,是由消费者主动请求生产者去向服务器发送请求,然后再使用从服务器返回的数据;而在推送过程中,生产者会按自己的节奏产生数据,并决定何时把数据推送给消费者,此时的消费者只需关注要对数据做出何等反应即可。可见,与拉取方式相比,使用推送方式可以将生产者与消费者解耦:生产者只关注生产数据以及何时推送,而消费者只需关注如何处理这些数据。 因此,在angular的体系中,生产者就是我们的服务(service),而消费者就是我们的前端模板。由于推送体系可以将生产者和消费者解耦,因此我们在angular中可以采用依赖注入的模式进行开发,让service专注于处理和服务器通信的部分,而前端模板只需调用不同的service,并对返回的数据进行处理即可(这张图可以再放一遍了):
我们再来看一张表格,看看在拉取和推送体系中使用的何种概念来获得数据:
| 单个值 | 多个值 |
---|
拉取 | Function | Iterator | 推送 | Promise | Observable |
在拉取体系中,如果需要获得单个值,我们使用最常用的概念——函数(Function)。显然,一个函数一次只能返回一个值;如果在拉取体系中要想获得多个值,我们使用迭代器的概念(Iterator),每调用一次迭代器的next函数就返回一个值。在推送体系中,如果获得单个值,我们使用Promise;而当我们需要在推送体系中返回多个值的时候,我们就需要使用这篇博客的主角——Observable了,它可以在推送体系中返回多个值,像Iterator一样。需要注意的是,Observable是一个对象,而不是函数。它只有被调用时才会返回它“生产”的数据。 Observable可以同时返回三种类型的值:next,error和complete。next返回的是正常值,可为数字、字符串等等;error顾名思义,返回异常或错误;而complete表示在返回complete后就不再返回任何值了。next可以有0到无穷个,而error和complete二者只会返回一个。 现在,我们就可以来看registerUser这个函数了,它会将传入的数据以post方式发送给指定的url,然后返回一个Observable对象,对象中存储着这次请求得到的数据。
2 观察者Observer
看完了Observable的介绍,下面让我们来看一下Observer的概念。Observer可直译为观察者,是Observable的消费者,用于使用Observable从服务器中获得的数据。 标准的Observer有三个回调函数,分别对应Observable返回的三种数据类型:
var observer = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
其实,Observer的本质就是这三个回调函数的集合。 要想让Observer来使用Observable的数据,我们需要调用Observable的subscribe方法,并将Observer作为参数传入。 让我们再复习一下我们前端中的registerUser函数,看看它是怎么使用Observable对象的:
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);
alert('注册失败');
}
}
)
}
}
这里我们使用了registerService.registerUser(this.newUser)来获得了一个Observable对象,随后调用它的subscribe,并定义一个回调函数作为subscribe的参数,此时subscribe函数内部会建立一个observer对象,并使用第一个回调函数来处理next的数据。 所以,我们的代码中就使用data来接收service返回的数据,并根据data的不同而做出不同的处理。 通过以上的介绍,大家应该明白了angular为什么要使用service,以及为什么要使用subscribe的写法来处理与服务器的通信。 在理解了angular与后端服务器通信的原理后,我们就可以继续开发我们的用户登录功能了。
十 用户登录功能的开发
1 前端部分
我们打开cmd,输入以下命令,建立login组件:
ng g c login
然后打开login.component.html,输入以下代码:
<form nz-form [formGroup]="loginForm" class="login-form" (ngSubmit)="onSubmit()">
<nz-form-item>
<nz-form-control>
<nz-input-group [nzPrefix]="prefixUser">
<input type="text" nz-input formControlName="username" placeholder="用户名" />
</nz-input-group>
</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-control>
</nz-form-item>
<nz-form-item>
<nz-form-control>
<a class="login-form-forgot" class="login-form-forgot">忘记密码</a>
<button nz-button class="login-form-button" [nzType]="'primary'">登录</button>
Or
<a href="/register">register now!</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>
我们在这里依然使用nz-form控件,构造一个包含两个输入框的表单: 然后,我们打开app.module.ts文件,将NzMessageModule和CookieService引入:
import { NzMessageModule } from 'ng-zorro-antd/message';
import { CookieService } from "ngx-cookie-service";
@NgModule({
declarations: [
],
imports: [
NzMessageModule,
],
providers: [CookieService],
bootstrap: [AppComponent]
})
providers: [CookieService],
NzMessageModule模块用于在页面上弹出一些小消息,而CookieService是第三方库,用于处理Cookie的相关事宜。 然后,我们建立loginUser.ts文件,在其中定义loginUserData这一interface,用于用户登录时的数据传递:
export interface loginUserData {
username: string;
password: string;
}
接下来,我们建立用户登录的service:
ng g s login
在login.service.ts文件中,输入以下代码:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { loginUserData } from '../login/loginUser';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoginService {
constructor(private http:HttpClient) { }
loginUser(userData:loginUserData):Observable<loginUserData>{
return this.http.post<loginUserData>('http://localhost:8000/login',userData);
}
}
这个和用户注册的service代码大致一样,只是把url换成了用户登录功能的。 最后,我们打开login.component.ts文件,开始编写组件的代码,以及在login.component.css中设置一下样式:
import { Component, OnInit } from '@angular/core';
import { FormControl,FormGroup } from '@angular/forms';
import { LoginService } from '../service/login.service';
import { loginUserData } from './loginUser';
import { Router } from '@angular/router';
import { NzMessageService } from 'ng-zorro-antd/message';
import { CookieService } from 'ngx-cookie-service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm = new FormGroup({
username:new FormControl(''),
password:new FormControl(''),
});
userData:loginUserData;
constructor(private loginService:LoginService,private router:Router,private message:NzMessageService,private cookies:CookieService) {
this.userData = {username:'',password:''}
}
onSubmit(){
this.loginUser()
}
loginUser():void{
this.userData = this.loginForm.value;
this.loginService.loginUser(this.userData).subscribe((data:any)=>{
if(data)
{
if (data['result'] == 'Success')
{
this.cookies.set('currentuser',this.userData.username,30,'/','localhost',true,"Lax")
this.router.navigateByUrl('/')
}
else
{
this.message.create('error','用户名或密码错误!')
this.loginForm.setValue({username:'',password:''})
}
}
})
}
ngOnInit() {
}
}
# login.component.css
.login-form {
max-width: 300px;
}
.login-form-forgot {
float: right;
}
.login-form-button {
width: 100%;
}
在这个组件里,我们定义了loginForm来接收前端发来的表单数据,并在构造函数里声明了CookieService的对象;而在loginUser函数中,我们如法炮制,用service将用户登录所需数据传给了后端,如果登录成功的话,则使用CookieService设置cookie,并重定向回我们的首页;如果登录失败,则使用NzMessageModule弹出一个错误信息,信息内容为用户名或密码错误。 现在,我们可以在app-routing.module.ts中为登录组件设置路由了:
const routes: Routes = [
{path:'login',component:LoginComponent},
];
记得要回到mainlayout.component.html中,把登录的菜单项挂上路由:
<li nz-menu-item routerLink='/login'>登录</li>
在这篇博客中,我们介绍了angular调用服务背后的原理,以及实现了用户登录的前端部分。在下篇博客中,我们将继续实现用户登录的后端部分,以及介绍路由事件的相关知识,希望大家继续关注~
|