项目二
0 效果展示
在这里插入图片描述
一 项目初始化
1.搭建项目
ng new '项目名'(等到开始出现进度条的时候打断)
cd '项目目录'(进入你刚创建的项目中)
cnpm install(安装需要用的包等)
npm start(启动项目)
2. 初始化组件
//组件的划分与页面相关
cd '项目目录'
ng g component '组件名称'
//可以在app.component.html文件中引入每个组件对外提供的标签,标签名名称可以在组件对应的ts文件中查看
3.模板渲染
- 将已经准备好的静态页面的相关关内容添加到对应组件的html文件中,可以通过在app.component.html加入标签验证是否添加成功
- 为组件下载需要的模板,可以在原来的静态页面中查看当前页面原本引用了哪些页面,哪些是下载的,哪些是手动导入的(下载的时候@是为了规定版本)
cnpm i -S bootstrap@3.3.7 - 在项目的styles.css文件中引入下载的文件(引入下载的文件到styles.css中而不是每个组件对应的.css中,因为这些样式是通用的)
@import url(‘bootstrap/dist/css/bootstrap.css’) - 将我们自己准备的样式添加到对应组件的对应的.css文件中;如果我们自己的写的css文件是全局样式,就可以将其粘贴到styles.css文件中
- 将单个组件准备好之后,我们需要在首页的html文件中添加创建好的组件,将首页布局展示出来-如下:
<app-navbar></app-navbar>
<div class="container-fluid">
<div class="row">
<app-sidebar></app-sidebar>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<app-contact-list></app-contact-list>
</div>
</div>
</div>
二 核心功能实现:
1.处理路由
- 在首页中我们可以点击链接跳转到不同的页面,需要使用到angular的路由功能,这条指令的意思就是生成路由模块添加到我们的项目中
ng generate module app-routing --flat --module=app
//指令执行结束可以看到app目录下多了相应的ts文件,此外在app.module.ts文件中可以看到路由module也更新进去了
- 配置路由表:当请求到XXX路径的时候,就导航到XXX组件
a) import { Routes, RouterModule } from '@angular/router';
//在app-routing.module.ts文件中导入相应模块,视频中老师主动下载主动配置但是我的好像是自动下载自动配置
b)
import { SigninComponent } from './signin/signin.component'
import { SignupComponent } from './signup/signup.component'
const routes: Routes = [
{
path: 'signin',
component: SigninComponent
},
{
path: 'signup',
component: SignupComponent
}
];
//在路由数据中直接配置就好,可以自动导入相应的组件
- 配置路由出口以及路由导航链接
a) 路由出口,配置之后可以通过在浏览器中输入对应的path得到对应组件的页面
<router-outlet></router-outlet>
b) 分析首页的路由情况,发现路由情况主要由标签决定,并且内容不能写死,应该由路由导航出口决定;两重路由,得出需要嵌套路由的结论–方式:为当前组件创建模板布局组件
//路由出口
<router-outlet></router-outlet>
c) ng g component layout //在控制台为当前项目创建模板布局组件,将下面的首页中应该显示的组件配置到layout组件的html文件中,注意注释掉的部分
<div class="container-fluid">
<div class="row">
<app-sidebar></app-sidebar>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<--<app-contact-list></app-contact-list>-->
</div>
</div>
</div>
d) 为layout组件配置路由,方式同前面signin等,之后可以在浏览器中通过路径定位到Layout布局页面,可以看到中间部分没有什么数据,因为中间那部分没有路由到
e) 由于要创建的是嵌套路由,此时在上面的html中注释掉的部分要加上路由标签
<router-outlet></router-outlet>
f) 在配置路由的文件中配置子路由如下,使用path访问的时候就可以得到路由
path: 'contacts',
component: LayoutComponent,
children: [
{
path: '',
component: ContactListComponent
}
//当我们访问contacts的时候,会先把LayoutComponent组件渲染出来,然后把children中的path为空的路由渲染到LayoutComponent组件的路由出口
g) 配置首页路由
//请求根路径的时候跳转到contacts组件
{
path: '',
redirectTo: '/contacts',
pathMatch: 'full' //路径必须完全匹配的是时候才重定向
//没有上面会报错
},
h) 按照相同的方式将新增/编辑联系人添加为contacts的子路由;同理将tag的增删改添加为tags的子路由,之后可以在浏览器中进行验证
2.登录注册页面表单验证
- 在ts问文件中定义表单项,在视图文件中双向绑定(记得表单验证需要额外加入需要使用到的模块)
import { FormsModule } from '@angular/forms' //全局--imports处也要修改
//注意angular会建议我们使用了双向绑定的控件添加name属性
//双向绑定是否成功可以通过{{}}进行一遍输入一遍展示的验证
- angular处理表单验证–参建官网,根据需要的验证模式直接照着写
<input type="email" id="inputEmail" class="form-control" name="email"
placeholder="Email address"
required
email="true"
[(ngModel)]="signupForm.email"
#email="ngModel">
//是根据required和email="true"这两个条件来进行验证的,当然要先使用#email="ngModel"绑定,属于angular固定语法
<div *ngIf="email.invalid && (email.dirty || email.touched)"
class="alert alert-danger">
<div *ngIf="email.errors.required">
Email is required.
</div>
<div *ngIf="email.errors.email">
Email is invalid
</div>
</div>
//密码的验证方式非常相似,只是密码原本没有你做啥呢验证属性,我们为其加上minlength,以及Maxlength两个属性进行验证
<div *ngIf="password.invalid && (password.dirty || password.touched)"
class="alert alert-danger">
<div *ngIf="password.errors.required">
password is required.
</div>
<div *ngIf="password.errors.minlength">
password minlength 6
</div>
<div *ngIf="password.errors.maxlength">
password maxlength 10
</div>
</div>
3.http请求的发送
- 在总的项目app.module.css中导入HTTP用的到module
import { HttpClientModule} from "@angular/common/http" //之后再Imports中注册
- 再我们要用到该服务的组件的ts文件中加入该module以及RouterModule
import { HttpClient } from "@angular/common/http" //这里是HttpClient
import { Router } from "@angular/router"
- 在方法中发送post请求
//组件的构造方法中加入服务
constructor(
private http: HttpClient,
private router: Router
) { }
//钩子函数
ngOnInit() {
}
//用于接收表单提交的数据
signupForm={
email: '',
password: ''
}
//用于接收返回的错误信息
email_err_msg=''
public signup(){
// 1. 表单验证--好像不是前端验证?
// 2. 获取表单数据
const formData=this.signupForm;
// 3. 发起 http 请求和服务端交互
this.http.post('http://localhost:3000/users',formData)
.toPromise()
.then((data: any)=>{
console.log(data)
// 4. 根据响应结果做交互处理
this.email_err_msg='';
//将token和返回的用户信息存储到本地
window.localStorage.setItem('auth_token',data.token);
window.localStorage.setItem('user_info',JSON.stringify(data.user));
//定向到list展示页面
this.router.navigate(['/']);
})
.catch(err=>{
//数据进行序偶无信息处理
if(err.status===409){
this.email_err_msg='邮箱已经注册'
}
})
}
4. 登录成功之后实现页面的跳转
1) import { Router } from "@angular/router" //在需要使用路由的组件ts文件中导入
2) //在组件的构造方法中声明路由就像声明Http那样
constructor(
private http: HttpClient,
private router: Router
) { }
3) //配置返回数据处理之后的提跳转
this.router.navigate(['/']);
5 基本的权限验证
- 存储token(本地存储可以使用浏览器抓包看到)
window.localStorage.setItem('auth_token',data.token);
//由于联系人列表展示页面在没有登陆的时候也能够访问,我们需要做出权限控制(后端的控制主要依靠shiro来实现),前端如何设置呢?前端进行去权限控制之后后端还需要进行权限控制吗?
- 在即将跳转的组件对应的ts文件的钩子函数ngOnInit()中进行判定,如果没有收到token,就跳转到登录页面
//由于需要用到路由需要先导入,然后在构造函数中进行声明
ngOnInit() {
//本地有token才能够跳转到登录页面,没有的时候不跳转(注意token可以手动删除,同时注意过期时间,token与session和cookie很像)
const token=window.localStorage.getItem('auth_token');
if(!token){
//如果没有token说明没有注册或者没有登录?,跳转到注册页面--应该是跳转到登录页面更有道理
this.router.navigate(['/signup']);
}
}
- //但是由于众多页面都需要这样进行规定,重复写就很没意思,所以我们使用路由guards进行统一处理
a) 在app下创建auth-guard.service.ts文件
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
//由于所有的需要token的接口都会判断token是否正确传输,这样进行配置以后,如果没有得到token
//那么页面就会跳转到注册页面
canActivate() {
const token=window.localStorage.getItem('auth_token')
if(!token){
this.router.navigate(['/signup']);
}
return true;
}
}
- 使我们的配置生效
a) 在全局路由配置文件app-routing.module.ts文件中导我们刚刚配置的文件
//注意命名一定是大小写与驼峰的匹配
import { AuthGuard } from './auth-guard.service'
b) 在所有需要权限验证的路由界面申明权限
canActivate: [AuthGuard] //如下
//就是在路由之前先进入路由gaurds,验证通过返回true的时候继续导航,失败返回false的时候,按照路由guards的统一配置进行页页面跳转
c) 需要在app-routing.module.ts添加提供者
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [AuthGuard]
})
d) 同样的,可以在tags上面加上这些功能
//问题,进过验证同一个用户注册后可以跳转详情页面,登录之后也可以跳转详情页面,同一个用户登录和注册的时候生成的token值不一样,token是如何起作用的?
6.登录或者注册成功时候的用户信息显示
- 邮箱显示
a) 在登录/注册界面将用户信息写到本地
window.localStorage.setItem('auth_token', data.token)
//注意要将JSON格式的数据转换成字符串
window.localStorage.setItem('user_info', JSON.stringify(data.user))
b)在显示信息的组件的ts文件中得到数据
//取出数据后要将字符串解析为JSON格式,否则不能够使用user.来进行解析
user=JSON.parse(window.localStorage.getItem('user_info')|| '{}');
c) html中展示
<li><a href="#">{{ user.email }}</a></li>
d) 导航链接:从登录页面到注册页面/从注册页面到达登陆页面
<li><a routerLink="/signin">退出</a></li>
e) 用户退出,就是点击退出触发一个方法,方法中销毁token,然后路由到登录界面
7.请求头header的处理
1) //导入需要的模块
import { HttpClient,HttpHeaders } from '@angular/common/http'
2) //添加上头部信息
this.http.get("http://localhost:3000/contacts",{
headers: new HttpHeaders().set('X-Access-Token',token)
})
.toPromise()
3) //发现出登录注册之外的所有的请求都需要带上头信息,重复比较麻烦--使用HTTP的请求响应拦截HTTPIntecepter
a) 在app目录下新建global.interceptor.ts文件
import { Injectable } from '@angular/core';
import {
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class GlobalInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
return next.handle(req);
}
}
b) 在全局路由文件中配置导入相应的模块
//import { HTTP_INTERCEPTORS } from '@angular/common/http';
...
//import { GlobalInterceptor } from './global.interceptor';
c) //添加providers
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: GlobalInterceptor,
multi: true
}
//完成以上操作后,就实现了统一处理,接着在global.interceptor.ts中写统一拦截之后的处理逻辑就行
d) //在这里我们要做的是给每一个请求添加请求头
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
const token =window.localStorage.getItem('auth_token')
const authReq=req.clone({headers: req.headers.set('X-Access-Token',token)})
return next.handle(authReq);
}
//但是我发现还是只有/contracts的经过被拦截了(应该哪里写错了,又回到了最开始的单一设置header的方式)
e) //
8.处理业务
1) 新增联系人
a) //html进行简单修改,添加提交事件
<form (submit)="addContact()" form="addForm">
b) //导入http请求module,以及Router的module
c) //拼凑好参数之后发送到后台即可--注意路径的的写法
d) //注意<a>本身的部分功能要关闭
addContact(){
const token=window.localStorage.getItem('auth_token')
this.http.post('http://localhost:3000/contacts',this.formData,{
headers: new HttpHeaders().set('X-Access-Token',token)
})
.toPromise()
.then(data=>
//console.log(data)
//window.alert('添加成功!')
this.router.navigate(['/contacts'])
)
.catch(err=>{
window.alert('联系人添加失败')
})
}
2) 删除联系人
a) //链接中添加方法--注意提示
<a (click)="deleteById(item.id,$event)" href='#'>删除</a>
b) //写删除逻辑'
deleteById(id,e){
//阻止默认的链接事件
e.preventDefault();
c) //注意参数+ 弹框
if(!window.confirm('您确定要删除吗?')){
return;
}
const token=window.localStorage.getItem('auth_token');
this.http.delete(`http://localhost:3000/contacts/${id}`,{
headers: new HttpHeaders().set('X-Access-Token',token)
})
.toPromise()
.then((data: any)=>{
console.log(data)
window.alert('删除成功!')
this.getContacts()
})
.catch(err=>{
console.log(err)
window.alert('删除失败')
})
}
3) 编辑联系人
a) 传递id
//更改路由文件,在原来的path路径后面添加
{
path: 'edit/:id',
component: TagEditComponent
}
b) 绑定动态路径参数
<a routerLink="['/contacts/edit',item.id]">编辑</a>
c) 在目标组建中获得动态路由的参数
1) import { Router,ActivatedRoute } from '@angular/router'
2)//得到id
ngOnInit() {
const contactId=this.route.snapshot.params.id
}
3) //进行数据回显
getContactById(id){
const token=window.localStorage.getItem('auth_token')
this.http.get(`http://localhost:3000/contacts/${id}`,{
headers: new HttpHeaders().set('X-Access-Token',token)
})
.toPromise()
.then((data:any)=>{
this.formData=data
})
.catch(err=>{
console.log(err)
})
}
4) //数据的编辑
editContact(){
const id=this.formData.id;
const token=window.localStorage.getItem('auth_token')
this.http.patch(`http://localhost:3000/contacts/${id}`,this.formData,{
headers: new HttpHeaders().set('X-Access-Token',token)
})
.toPromise()
.then((data: any)=>{
this.router.navigate(['/contacts'])
})
.catch(err=>{
window.alert('编辑请求失败')
})
}