博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用Angular轻松搭建CMS页面
阅读量:6613 次
发布时间:2019-06-24

本文共 11176 字,大约阅读时间需要 37 分钟。

Angular(Angular 2+ )是一套现代的 WEB 开发框架,它采用模块化开发,提供一套完整的开发支持,使开发者能更专注于业务逻辑,提高生产效率。
CMS(内容管理系统),提供对内容的增、删、改、查等功能。
本文介绍如何用 Angular 搭建一个 CMS 系统,文章重点关注流程,更多技术细节请参考 。

目标

实现简易用户管理功能,查看。

  • 编辑页:支持新建用户,支持修改用户信息
  • 列表页:展示用户数据,支持分页查询,支持删除用户

搭建环境

确保设备已安装 , 且满足 node 8.x 和 npm 5.x 以上的版本。

安装 。它包含一套命令行指令,可以帮助开发者快速创建项目、添加文件、以及执行项目运行、打包等任务。

npm install -g @angular/cli

创建Angular项目

使用 Angular CLI 提供的ng new命令创建一个新项目。Angular CLI 会在当前目录创建一个指定命名的新项目,创建过程中会自动安装项目所需依赖,如果在公司内网这一步需要配合代理进行。运行下列命令创建并启动一个 CMS 项目。

ng new cmscd cmsng serve --open

使用--open,在编译完成后会自动打开浏览器并访问 :4200/,可以看到一个 Angular 项目启动了。其他比较常用的是参数有,

--port 指定端口号--proxy-config 代理配置文件--host fe.cms.webdev.com /*在需要读取cookie的情况下会有用*/

搭建页面骨架

模块与组件

Angular 采用模块化的开发方式。

模块是一组功能的集合。模块把若干组件、服务等聚合在一起,它们共享同一个编译上下文环境。页面的每一个小部分都可以看作是一个组件。
组件包含组件类和组件模版。模版负责组件的展示,可以使用 Angular 的模版语法对 html 进行修改。组件类实现组件的逻辑部分,可以通过注入服务去实现一些数据交互逻辑。

Angular CLI 初始化项目中有唯一的一个模块—— AppModule 模块。它是一个根模块,页面从这里启动,它下面可以包含子模块和组件。为了演示方便,在项目中不再新建模块,只通过组件去实现不同页面的展示。

新建两个组件:list 负责数据管理,edit 负责表单编辑。除此之外,还需要一个 nav-side 组件作为页面导航,负责 list、edit 的切换。用 命令创建这三个组件。下面几个命令是等价的。

ng generate component nav-sideng g component editng g c list

试试将它们添加到页面中,在模版中创建它们。

在页面上可以看到,这三个组件都被创建了。但我们需要在不同情况下分别展示 list 和 edit 组件,可以通过引入路由模块来实现。

路由

Angular 的Router模块提供了对路由对支持。在 Angular 中使用路由至少要做如下两个配置:

1、定义路由。Angular 路由(Route)是一个包含 path 和 component 属性对对象数组。path 用来匹配URL路径,component 则告诉 Router 在当前路径下应该创建哪个组件。
2、添加路由出口。在页面上添加<router-outlet>元素,当路由到的某个组件时,会在当前位置展示组件的视图。

定义页面需要的路由。Edit 路由上定义了一个id参数,通过它可以把用户ID传给组件。

import { RouterModule, Routes } from '@angular/router';const appRoutes: Routes = [ { path: 'list', component: ListComponent }, { path: 'edit/:id', component: EditComponent }, { path: 'edit', redirectTo: 'edit/create', pathMatch: 'full'}, { path: '', redirectTo: '/list', pathMatch: 'full'} // 默认定向到list];@NgModule({ imports: [ RouterModule.forRoot(appRoutes), // other imports here ], ...})export class AppModule { }

在模版中定义路由出口,之前的 edit 和 list 模块被路由出口代替。当路由匹配 edit 或 list 时,它们会在router-outlet的位置被创建。

在 nav-side 中使用路由跳转。绑定routerLink属性,下面使用两种方式,后一种方式支持传入更多参数。此外还绑定了routerLinkActive属性,它支持传入CSS类,当当前路由被激活时CSS类就会被添加。

现在我们会看到页面效果如图。点击侧边栏,可在列表页和编辑页之间来回切换。

图片描述

至此,页面骨架搭建完成。

列表页实现

简单梳理列表页需要实现的内容。

  • 功能拆分:数据展示、查询、删除
  • 页面划分:表格、分页、搜索框

数据定义

在开始页面实现之前,需要做一些准备工作,首先需要设计列表页的数据。

Angular项目中默认使用TypeScript开发,在TS中我们可以通过Interface实现数据类型的定义。
定义Interface的好处在于可以规范数据类型,编辑器及代码编译阶段都会对数据类型做检查,可以减少由于类型而导致的问题的产生,明确的类型定义也便于后期维护。

新建一个data.interface.ts文件,并定义用户、列表、分页、列表搜索参数的数据格式。

export interface IUser {    id?: number;    nick: string;    sex: 'male'|'female';}export interface IList {    data: IUser[];    pager: IPager}export interface IPager {    currPage: number;    totalPage: number;}export interface ISearchParams {    page?: number;    keyword?: string;}

数据模拟

在一些场景下,为了模拟数据请求,前端需要实现mock接口的功能。Angular提供了进行数据模拟。

我们可以创建项目中需要的一组数据,然后通过 REST API 请求获取数据。我们可以按照真实接口的样式去实现请求方法,在真正的接口准备好之后,只需要移除in-memory-data,就可以实现真实与模拟请求的无缝替换。

下面我们定义需要的数据。

import { InMemoryDbService } from 'angular-in-memory-web-api';export class InMemoryDataService implements InMemoryDbService { createDb() { const users = [ { id: 12, nick: 'Narco', sex: 'male' }, { id: 13, nick: 'Bombasto', sex: 'male' } ... ]; return {users}; }}

数据请求

HttpClient

Angular中实现HTTP请求需要引入HttpClientModule

HttpClient提供了一组 API 用来实现 HTTP 请求,并返回一个 Observable 类型的对象,可以对返回数据做流式处理,如错误拦截、数据转化等。

新建data.service.ts,用来实现数据请求。

在获取数据列表的请求中,我们使用map操作符对数据进行处理,获取需要的对应分页下的数据。

import { Injectable } from '@angular/core';import { HttpClient, HttpParams } from '@angular/common/http';import { Observable } from 'rxjs';import { map } from 'rxjs/operators';import { IList, IUser, ISearchParams } from './data.interface';@Injectable({ providedIn: 'root',})export class DataService { private url = 'api/users'; constructor(private http: HttpClient) {} getList(params: ISearchParams): Observable
{ let currPage = params.page, totalPage: number, limit = 6; return this.http.get
(this.url, { params: new HttpParams().set('nick', params.keyword) }).pipe( map((data: IUser[]) => { return { // 模拟分页 data: data.slice((currPage-1)*limit, (currPage)*limit), pager: { currPage: currPage, totalPage: Math.ceil(data.length / limit) } } })) } getUser(id: number): Observable
{ return this.http.get
(`${this.url}/${id}`) } deleteUser(id: number): Observable
{ return this.http.delete
(`${this.url}/${id}`) } addUser(data: IUser): Observable
{ return this.http.post
(this.url, data) } updateUser(data: IUser): Observable
{ return this.http.put
(this.url, data) }}

在AppModule中引入发送数据请求需要的HttpClientModule和本地数据获取需要的HttpClientInMemoryWebApiModule

import { HttpClientModule } from '@angular/common/http';import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';import { InMemoryDataService } from './in-memory-data.service';@NgModule({ imports: [ HttpClientModule, HttpClientInMemoryWebApiModule.forRoot( InMemoryDataService, { dataEncapsulation: false } ) // other imports here ], ...})export class AppModule { }

组件实现

下一步,需要在 list 组件内调用 DataService 获取列表数据并展示。这里使用到了 Angular ——ngOnInit,在组件 Init 之后执行页面逻辑。

接下来会使用到 Observale 和 RXJS 操作符,相关知识点参考 ,

由于 DataService 返回一个包含列表数组及分页信息的 Observable 类型的数据,我们需要将这两部分数据分离并展示。下面代码中,通过一系列流的操作,我们把分页数据提取给了 pager 对象,列表数组使用一个 Observable 类型的对象表示—— listData$。

将 listData$ 绑定到模版上,通过async [pipe](https://angular.io/guide/pipes)可以实现 Observable 的订阅。Observable 在被订阅后,每次更新 Observer 都会受到新数据,即页面上的数据都会刷新。由于 updateList$ 是BehaviorSubject类型,只需要调用next方法即可实现数据的刷新。

export class ListComponent implements OnInit { pager: IPager = { currPage: 1, totalPage: 1 } as IPager; listData$: Observable
; updateList$: BehaviorSubject
= new BehaviorSubject
(1); constructor(private service: DataService) { } ngOnInit() { this.listData$ = this.updateList$ .pipe( switchMap((page: number) => { // 获取列表数据 return this.service.getList(Object.assign({ page: page }, this.searchForm.form.getRawValue())).pipe( catchError(() => of({ data: [], pager: { currPage: 1, totalPage: 1 } }))) }), tap((list: IList) => { this.pager = list.pager }), map((list: IList) => list.data) ) } //删除用户 deleteUser(id: number) { this.service.deleteUser(id).subscribe(() => { //刷新列表 this.updateList$.next(this.pager.currPage); }) }}
ID 昵称 性别 操作
{
{data.id}}
{
{data.nick}}
{
{data.sex === 'male'? '男': '女'}}
编辑 删除

组件间数据交互

分页组件

实现一个简单的分页组件,展示当前页码和总页数,并提供一个输入框可以填写需要跳转到的页面。

新建一个 pagination 组件。组件接收 IPager 类型的参数,并展示 pager 内容。当跳转按钮被点击时,向外发出 pageChange 事件,并把需要跳转到的页码给出。父组件( ListComponent )需要在模版中给 pagination 组件传入 pager 属性的值,并监听 pageChange 事件。这里使用了 Angular 的@Input@Output定义了组件的输入输出属性。

对于回车跳转的方式,可以直接监听 Input 上的 keyup 事件,也可以通过 RXJS 的fromEvent监听 keyup 事件,当监听到回车时调用页面跳转方法。

export class PaginationComponent implements OnInit { targetPage: number; @Input() pager: IPager; @Output() pageChange: EventEmitter
= new EventEmitter
(); ngOnInit() { fromEvent(document.getElementById('input'), 'keyup') .pipe(filter((event: KeyboardEvent) => event.key === 'Enter')) .subscribe(() => { this.onPageChange(); }) } onPageChange() { this.pageChange.emit(+this.targetPage); this.targetPage = null; }}
跳转
{
{pager.currPage}} / {
{pager.totalPage}}
onPageChange(page: number) { this.updateList$.next(page);}

搜索组件

对于搜索组件,它需要将搜索表单内容与列表页共享,这里通过@ViewChild的方式共享数据,它提供了父组件获取子组件实例的方法,通过组件实例可以获取到组件内的属性。

新建 searh-form 组件,使用 的模式构建一个搜索表单。

import { Component, OnInit, Output, EventEmitter } from '@angular/core';import { FormGroup, FormBuilder, Validators } from '@angular/forms';...export class SearchFormComponent implements OnInit { form: FormGroup; @Output() search: EventEmitter
= new EventEmitter
(); constructor(private fb: FormBuilder) { } ngOnInit() { this.form = this.fb.group({keyword: ['']}); } onSubmit() { this.search.emit(); }}
@ViewChild(SearchFormComponent) searchForm: SearchFormComponent;ngOnInit() {this.listData$ = this.updateList$ .pipe( switchMap((page: number) => { return this.service.getList(Object.assign({ page: page }, this.searchForm.form.getRawValue())).pipe( catchError(() => of({ data: [], pager: { currPage: 1, totalPage: 1 } }))) }), tap((list: IList) => { this.pager = list.pager }), map((list: IList) => list.data) )}onSearchDataChange() { this.updateList$.next(1);}

至此,我们实现了用户的展示、查询、删除操作,列表页完成。

图片描述

编辑页实现

简单梳理编辑页需要实现的内容。

  • 功能拆分:数据新增、修改
  • 页面划分:标题、表单

标题

在编辑页需要根据用户ID区分是否新建用户。在路由配置中我们已经配置了编辑页最后一个参数为ID,并设置对于新建用户(没有用户ID)的情况下路由统一跳转到 create。因此我们需要在页面中获取路由ID参数,根据是否 create 判断是否为新建用户,并保存用户ID。

这里采用了监听路由参数的方式来获取路由参数,在页面URL发生改变时,用户ID会及时更新。

userId: string;construct( ... private route: ActiveRoute) { this.route.paramMap.subscribe((params: ParamMap) => { this.userId = +params.get('id') || null; })}

{
{!userId? '新建用户': ('编辑用户 - ')}}{
{userId}}

表单

新建

同样的,我们引入 Reactive-Form 模块,通过数据模型来渲染表单。这里我们加入了表单校验配置,设置 nick 和 sex 都必填,校验结果可以通过invalid方法获取。并且在校验失败时,将提交按钮置灰。

表单数据的提交就是请求 DataService 的 addUser 方法,可以在提交成功后通过路由方法跳转到列表页。

ngOnInit() { this.userForm = this.fb.group({ nick: [null, Validators.required], sex: [null, Validators.required] })}onSubmit() { this.dataservice.addUser(this.userForm.getRawValue()).subscribe(() => { this.router.navigate(['/list']); })}

图片描述

修改

在用户ID存在时,需要获取用户信息进行展示。DataService 已经实现了数据获取方法,在拿到用户信息后,可以通过patchValue对 userForm 的数据进行修改。

最后我们修改一下 submit 方法,让它能兼容新建和保存两种模式。

construct( ... private route: ActiveRoute) { this.route.paramMap.subscribe((params: ParamMap) => { this.userId = +params.get('id') || null; this.userId && this.getFormData(); })}private getFormData() { this.dataservice.getUser(this.userId).subscribe((data) => { this.userForm.patchValue({nick: data.nick, sex: data.sex}); }) }onSubmit() { let submitType = this.userId? 'updateUser': 'addUser'; let formData = this.userForm.getRawValue(); this.userId && (formData.id = this.userId); this.dataservice[submitType](formData).subscribe(() => { this.router.navigate(['/list']); })}

图片描述

项目打包及部署

如果需要把项目打包并部署到服务器上,只需要运行ng build命令即可完成打包,可以配置--prod参数以选择 的方式打包。打包后的文件会被保存在angular.json中配置的outputPath路径下。

文件的引用路径可以查看打包后的 index.html,并且可以在 angular.json 中修改配置路径。

最后

整套流程下来,我们构建了一个简单但是完整的 CMS 系统,涉及了 Angular 中大部分基础知识点。后续可参考官方文档,增强系统功能,运用更多 Angular 特性。

转载地址:http://kgaso.baihongyu.com/

你可能感兴趣的文章
Android应用程序组件Content Provider的共享数据更新通知机制分析(3)
查看>>
敏友的【敏捷个人】有感(11): 敏捷个人线下活动有感
查看>>
刺激用户危机意识,实现快速盈利的营销思维
查看>>
英特尔嵌入式突围
查看>>
WIN FORM 多线程更新UI(界面控件)
查看>>
【常见问题】系列01:双击文件夹打开新窗口
查看>>
将字符串中从n位开始以*代替
查看>>
access 2007创建表关系
查看>>
JDBC公共动作类
查看>>
JUnit单元测试
查看>>
[logstash-input-file]插件使用详解
查看>>
HDU 3103 Shoring Up the Levees(计算几何 搜寻区域)
查看>>
spring mvc模拟用户增删改查以及登录和上传文件的相关流程
查看>>
11.并发包阻塞队列之LinkedBlockingQueue
查看>>
植物大战僵尸
查看>>
Inner Functions - What Are They Good For?
查看>>
原创文章
查看>>
python之文件操作-复制、剪切、删除等
查看>>
LAMP环境搭建
查看>>
css3实现可以计算的自适应布局——calc()
查看>>