使用ng2-admin搭建成熟可靠的后台系统 -- ng2-admin(五)
完善动态表单组件
升级Angular 4.1 -> 4.3
添加
json-server
模拟数据创建自己的 http
完成一次表单提交
升级Angular 4.1 -> 4.3(版本大于 4.3.0 可跳过)
因为 httpClient 支持的最低 Angular 版本为 4.3, 所以需要先升级我们的 Angular
如下图 配置 package.json
然后需要修改一个配置项,兼容到 4.3.0 版本
tsconfig.json
添加一行
"paths": { "@angular/*": ["../node_modules/@angular/*"] }
这样便完成了版本的升级
添加 json-server
模拟数据
我们完成一个数据查询,提交需要一些模拟数据,json-server
工具可以帮助我们,全局安装
npm i json-server -g
在 package.json
中的 scripts 添加一行
"db:mock": "json-server --watch ./db/db.json"
我们现在需要创建一个 json 文件,用于装载我们的模拟数据,根据上述命令,在项目根目录创建
db/db.json
{
"user": [
{
"id": 1,
"firstName": "张小",
"emailAddress": "15135131@qq.com",
"brave": "solid"
}
]
}
打开一个命令行窗口,在项目文件夹执行
npm run db:mock
我们的 json-server
就启动了,现在来回到我们的项目
创建自己的 http
在提交表单之前,我们需要 ajax
去提交我们的数据,这时候涉及到服务,数据验证,处理 response
,这时候需要创建一套 http/httpClient 去负责这些任务。
创建一个 http 工具类,负责提供 header, host, 以及完成拼接 url, 参数的工作。 根据我们的 json-server
配置,来创建这个文件。
api/http/http.service.ts
import { Injectable } from "@angular/core";
import { HttpHeaders } from "@angular/common/http";
@Injectable()
export class HttpComponentUtil {
public headers = new HttpHeaders({
"Content-Type": "application/json"
});
private url: string = "http://localhost:3000/";
public getUrl(url: string): string {
return this.url + url;
}
}
创建 http 的统一拦截服务,做统一拦截,并且在未来做一些通用的工作
api/http/noopInterceptor.ts
import { Injectable } from "@angular/core";
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpResponse
} from "@angular/common/http";
@Injectable()
export class NoopInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const started = Date.now();
return next
.handle(req)
.do(event => {
if (event instanceof HttpResponse) {
const elapsed = Date.now() - started;
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
}
});
}
}
http 的统一拦截,还需要在 appModule.ts
中单独注册
app.module.ts
@NgModule({
...
providers: [
...
{
provide: HTTP_INTERCEPTORS,
useClass: NoopInterceptor,
multi: true,
}
]
});
当 http 请求出错时,需要做一些错误处理,现在来创建一个服务,做统一的错误处理
api/http/handle.service.ts
import { Injectable, Inject } from '@angular/core';
import { Location } from '@angular/common';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import swal from 'sweetalert2';
@Injectable()
export class HandleService {
constructor(
private router: Router,
private _location: Location
) {
};
// 处理系统错误
handleError(err: any): Observable<any> {
let errMsg = '发生未知错误,请重试';
if (typeof err == 'object' && err.status !== undefined) {
if (err.status == 404) {
errMsg = '服务器处理异常,请重试';
} else if (err.status == 401) {
swal('当前页面无权限查看', '', 'warning');
this._location.back();
return Observable.empty();
} else if (err.status == 504) {
errMsg = '服务器请求超时,请重试';
} else if (err.status == 503) {
errMsg = '相关服务正在部署发布,请稍等';
} else {
errMsg = err.json().message;
}
}
swal(errMsg, '', 'error');
return Observable.empty();
}
// 处理returnCode 这里假定接口 200 通过后,后端返回的状态码为 returnCode
handleStatus(result: any): Observable<any> {
switch ((result.returnCode && String(result.returnCode)) || "201") {
case '201':
return Observable.of(result);
case '1000':
return Observable.of(result);
case '1001':
swal('当前页面无权限查看', '', 'warning');
this._location.back();
return Observable.empty();
case '1002': // 数据为空
return Observable.of(result);
default:
swal('无法识别的错误码,请联系管理员', '', 'error');
return Observable.empty();
}
}
}
上面有两个依赖注入,需要在 providers 注入,现在创建一个文件来提供注入
api/index.ts
import { HttpComponentUtil } from './http/http.service';
import { HandleService } from './http/handle.service';
export const API_SERVICE = [
HttpComponentUtil,
HandleService
];
pages/pages.module.ts
中注入这两个服务
@NgModule({
imports: [CommonModule, AppTranslationModule, NgaModule, routing],
declarations: [Pages],
providers: [
...API_SERVICE
]
})
到这里,http 服务创建完成,下半部分在表单提交时完成,属于应用层
完成一次表单提交
完成表单提交,需要用到 ajax, 我们将使用 httpClient 完成 ajax 的工作,我们需要先注入 HttpClientModule, 在 nga.module.ts
中注入,这里不贴代码了
注入完成后,现在来开始编写我们的各项请求实例。
因为是表单提交,所以我们新建一个服务,由它来完成表单提交的最后一步。
theme/components/dynamic-form/dynamic-form.service.ts
import { Observable } from "rxjs/Rx";
import { Injectable } from "@angular/core";
import { HttpClient, HttpEvent, HttpHeaders } from "@angular/common/http";
import { HttpComponentUtil } from '../../../pages/api/http/http.service';
import { HandleService } from '../../../pages/api/http/handle.service';
@Injectable()
export class DynamicFormService {
constructor(
private http: HttpClient,
private https: HttpComponentUtil,
private handleService: HandleService
) {}
public getList(url: string, params: {} = {}): Observable<any> {
return new Observable();
}
/**
*
*
* @param {string} url
* @param {{}} [params={}] 请求入参的 body,参数
* @returns {Observable<any>} 返回一个可供订阅的观察者对象
* @memberof DynamicFormService
*/
public saveQuery(url: string, params: {} = {}): Observable<any> {
let api_url: string = this.https.getUrl(url); // 利用公用的 http 服务,拼接获取url
return this.http.post(api_url, params, {
headers: this.https.headers
})
.map((res: any) => (<any>this.handleService.handleStatus(res)).value || undefined) // 捕获错误码
.catch(err => this.handleService.handleError(err)); // 捕获系统错误
}
}
上面构建了包含一个 saveQuery 功能的服务, 代码已经添加注释,可以仔细研读一下。
saveQuery 的两个参数,params 应该由动态表单直接获取提供,url 应该由页面提供, 所以 DynamicFormComponent
应该接入一个 Input
参数
@Input() config: FormConfig;
dynamic-form/form-base.ts
export interface FormConfig {
url: string;
}
现在需要在页面中,把 config
参数传入组件
user-add.component.ts
...
export class UserAddComponent {
public UserAddConfig: FormConfig = {
url: "user"
}
...
}
user-add.component.html
<h1>
新增用户组件
</h1>
<div class="user-form">
<dynamic-form [questions]="UserAddQuestions" [config]="UserAddConfig"></dynamic-form>
</div>
现在回到组件,我们将完成我们的提交表单操作,现在思考两个问题,提交成功后的操作
- 成功提示
- 返回到上一个页面
所以我们的组件应该是
dynamic-form.component.ts
import { Component, Input, OnInit } from "@angular/core";
import { Location } from '@angular/common';
import { FormGroup } from "@angular/forms";
import { QuestionBase } from "../dynamic-form-components/dynamic-form-base/question-base";
import { QuestionControlService } from "./question-control.service";
import { DynamicFormService } from "./dynamic-form.service";
import "style-loader!./dynamic-fom-components.component.scss";
import { FormConfig } from './form-base';
import swal from "sweetalert2";
@Component({
selector: "dynamic-form",
templateUrl: "./dynamic-form.component.html",
styleUrls: ["./dynamic-form.component.scss"],
providers: [QuestionControlService, DynamicFormService]
})
export class DynamicFormComponent implements OnInit {
@Input() questions: QuestionBase<any>[] = [];
@Input() config: FormConfig;
form: FormGroup;
payload = "";
constructor(
private qcs: QuestionControlService,
private service: DynamicFormService,
private _location: Location
) {}
ngOnInit() {
this.form = this.qcs.toFormGroup(this.questions);
}
onSubmit() {
this.payload = JSON.stringify(this.form.value);
this.service.saveQuery(this.config.url, this.payload)
.subscribe((res: Response) => {
console.log(res);
swal("success","","success").then(() => {
this._location.back();
});
})
}
}
这里使用到了 sweetalert2
组件,需要读者自行安装,并且在
pages.component.ts
中引入样式
import "style-loader!sweetalert2/dist/sweetalert2.min.css";
现在打开浏览器,来测试一下我们刚才的页面,测试结果如下
添加成功和失败,都有对应提示,并且提交成功后会返回到上一页,现在来看看 db.json
,如下图,数据也被添加进了json!
我们的数据已经存入,下章就来讲解,如何搭建一个动态表格组件,来展示我们的数据,后续会把增删改查功能一一介绍,在介绍完基础组件后,会注入 redux 方便我们的状态管理
(此章代码在ng2-admin 的 httpclient-submit 分支上,可以pull 下来,方便读者练习)