使用ng2-admin搭建成熟可靠的后台系统 -- ng2-admin(二)

使用ng2-admin搭建成熟可靠的后台系统 -- ng2-admin(二)


1.构建动态表单

  • 构建一个动态表单,动态生成控件,验证规则。

  • 创建一个input组件,一个select组件

  • 将组件注入到页面中并显示


创建组件目录

前半部分直接借鉴官方文档的写法,后期开启个性化定制后,功能强大!

theme/components 目录下创建一个文件夹 dynamic-form

项目目录结构如下,下面跟着步骤一步一步来构建

mark

创建对象模型

我们需要定义一个对象模型,用来描述表单功能需要的场景。相关功能非常之多(类似于 input select )。

先创建一个最基础的基类,名为 QuestionBase,文件为 dynamic-form-base/question-base.ts

export class QuestionBase<T>{
    value: T; // 值,类型可选
    key: string; // 字段名
    label: string; // 控件前的文字提示
    required: boolean; // 是否为必填
    disabled: boolean; // 是否禁用
    reg: string; // 正则
    prompt: string; // 验证不通过提示
    order: number; // 排序
    controlType: string; // 渲染类型
    constructor(options: {
        value?: T,
        key?: string,
        label?: string,
        required?: boolean,
        disabled?: boolean,
        reg?: string,
        prompt?: string,
        order?: number,
        controlType?: string
    } = {}) {
        // 设置各个值的默认值
        this.value = options.value;
        this.key = options.key || '';
        this.label = options.label || '';
        this.required = !!options.required;
        this.disabled = !!options.disabled;
        this.reg = options.reg || '';
        this.prompt = options.prompt || '';
        this.order = options.order || 0;
        this.controlType = options.controlType || '';
    }
}

在这个基础上,我们派生出两个类 InputQuestionSelectQuestion ,代表文本框和下拉框。

这么做的原因是根据不同的控件,进行个性化定制,以及合理规范,还有动态渲染出合适的组件

InputQuestion 可以通过type属性来支持多种HTML元素类型(例如: text number email) --- dynamic-form-base/question-input.ts

import { QuestionBase } from './question-base';

export class InputQuestion extends QuestionBase<string | number> {
    controlType = 'input';
    type: string;

    constructor(options: {} = {}) {
        super(options);
        this.type = options['type'] || '';
    }
}

SelectQuestion 表示一个带可选列表的选择框

import { QuestionBase } from './question-base';

export class SelectQuestion extends QuestionBase<string | number> {
    controlType = 'select';
    options: any[] = [];

    constructor(options: {} = {}) {
        super(options);
        this.options = options['options'] || [];
    }
}

最后别忘了用 index.ts 将这三个模型导出

接下来,我们定义一个 QuestionControlService,一个可以把模型转换为FormGroup的服务。 简而言之,这个FormGroup使用问卷模型的元数据,并允许我们设置默认值和验证规则

question-control.service.ts

import { Injectable } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

import { QuestionBase } from './dynamic-form-base';

@Injectable()
export class QuestionControlService {
    constructor() { }

    // 转化为控件
    toFormGroup(questions: QuestionBase<any>[]) {
        let group: any = {};

        questions.forEach(question => {
            if (question.required) {    // 选项为必填时
                if (question.reg) {       // 有正则的情况
                    group[question.key] = new FormControl(question.value || '', Validators.compose([Validators.required, Validators.pattern(question.reg)]));
                } else {
                    group[question.key] = new FormControl(question.value || '', Validators.compose([Validators.required]));
                }
            } else if (!question.required && question.reg) {     // 选项为非必填但是需要正则匹配的情况
                group[question.key] = new FormControl(question.value || '', Validators.compose([Validators.pattern(question.reg)]));
            } else {
                group[question.key] = new FormControl(question.value || '');
            }
        });
        return new FormGroup(group);
    }
}

动态表单组件

现在我们已经有了一个定义好的完整模型了,接着就可以开始构建一个展现动态表单的组件。

DynamicFormComponent 是表单的主要容器和入口

dynamic-form.component.html

<div>
  <form (ngSubmit)="onSubmit()" [formGroup]="form">
    <div *ngFor="let question of questions" class="form-row">
      <df-question [question]="question" [form]="form"></df-question>
    </div>

    <div class="form-row">
      <button type="submit" [disabled]="!form.valid">保存</button>
    </div>
  </form>

  <div *ngIf="payload" class="form-row">
    <strong>Saved the following values</strong><br>{{payload}}
  </div>
</div>

dynamic-form.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { QuestionBase } from './dynamic-form-base/question-base';
import { QuestionControlService } from './question-control.service';

@Component({
  selector: 'dynamic-form',
  templateUrl: './dynamic-form.component.html',
  providers: [QuestionControlService]
})

export class DynamicFormComponent implements OnInit {
  @Input() questions: QuestionBase<any>[] = [];
  form: FormGroup;
  payload = '';

  constructor(
    private qcs: QuestionControlService
  ) { }

  ngOnInit() {
    console.log(this.questions);
    this.form = this.qcs.toFormGroup(this.questions);
    console.log(this.form);
  }

  onSubmit() {
    this.payload = JSON.stringify(this.form.value);
  }
}

刚才创建的是一个组件入口,每个 question 都被绑定到了 <df-question>组件元素,<df-question> 标签匹配到的是组件 DynamicFormQuestionComponent,该组件的职责是根据各个问卷问题对象的值来动态渲染表单控件。

现在来创建它的子组件 DynamicFormQuestionComponent

dynamic-form-question/dynamic-form-question.component.html

<div [formGroup]="form">
    <div [ngSwitch]="question.controlType">
        <input *ngSwitchCase="'input'" [formControlName]="question.key" [id]="question.key" [type]="question.type">

        <select [id]="question.key" *ngSwitchCase="'select'" [formControlName]="question.key">
            <option *ngFor="let opt of question.options" [value]="opt.value">{{opt.key}}</option>
          </select>
    </div>
</div>

dynamic-form-question/dynamic-form-question.component.ts

import { Component, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';

import { QuestionBase } from '../dynamic-form-base/question-base';

@Component({
  selector: 'df-question',
  templateUrl: './dynamic-form-question.component.html'
})

export class DynamicFormQuestionComponent {
  @Input() question: QuestionBase<any>;
  @Input() form: FormGroup;

  get isValid() { return this.form.controls[this.question.key].valid }
}

从上面的组件可以看出,未来需要添加组件时,只需要添加一种类型,可以用 ngSwitch 决定显示哪种类型的问题。

在这两个组件中,我们依赖Angular的formGroup来把模板HTML和底层控件对象连接起来,该对象从问卷问题模型里获取渲染和验证规则。

注意:每个目录都需要用 index.ts 导出模块, 这里需要在 theme/components 将我们注册的组件统一导出。

注册组件

我们创建组件之后,需要将我们的组件注册到 module 中,这里选择 theme/nga.module.ts 注入我们的组件。

其实使用组件还需要注入 ReactiveFormsModule, ng2-admin 已经帮我们注册好了,所以我们这里只需要注册我们创建的组件即可

方法如下图,先引入

mark

然后添加至 NGA_COMPONENTS

mark

注册Service

DynamicForm 期望得到一个问题列表,该列表被绑定到@Input() questions属性。

QuestionService 会返回为工作申请表定义的那组问题列表。在真实的应用程序环境中,我们会从数据库里获得这些问题列表。

要维护控件,只要非常简单的添加、更新和删除 questions 数组中的对象就可以了。

切换到 pages 文件夹,开始使用我们上一章创建的 UserAddComponent

pages/user/user-list/user-add/user-add.service.ts

import { Injectable } from '@angular/core';

import {
  QuestionBase,
  InputQuestion,
  SelectQuestion
} from '../../../../theme/components/dynamic-form/dynamic-form-base';

@Injectable()
export class UserAddService {
  getQuestions() {
    let questions: QuestionBase<any>[] = [

      new SelectQuestion({
        key: 'brave',
        label: 'Bravery Rating',
        value: 'solid',
        options: [
          { key: 'Solid', value: 'solid' },
          { key: 'Great', value: 'great' },
          { key: 'Good', value: 'good' },
          { key: 'Unproven', value: 'unproven' }
        ],
        order: 3
      }),

      new InputQuestion({
        key: 'firstName',
        label: 'First name',
        value: 'Bombasto',
        required: true,
        order: 1
      }),

      new InputQuestion({
        key: 'emailAddress',
        label: 'Email',
        type: 'email',
        order: 2
      })
    ];

    return questions.sort((a, b) => a.order - b.order);
  }
}

需要在 html 文件以及 component 文件中显示,所以修改一下这两个文件

user-add.component.html

<h1>
    新增用户组件
</h1>
<div class="user-form">
  <dynamic-form [questions]="UserAddQuestions"></dynamic-form>
</div>

user-add.component.ts

import { Component } from '@angular/core';

import { UserAddService } from './user-add.service';
import { QuestionBase } from '../../../../theme/components/dynamic-form/dynamic-form-base/question-base';

@Component({
  selector: 'ngt-user-add',
  templateUrl: './user-add.component.html',
  providers: [UserAddService]
})

export class UserAddComponent {
  public UserAddQuestions: QuestionBase<any>[] = [];
  
  constructor(
    private service: UserAddService
  ) {
    this.UserAddQuestions = this.service.getQuestions();
  }
}

根据 Angular 的模块查找规则,所以这里还需要把 NgaModule 注入到 user.module.ts中,如下图

mark

然后打开浏览器,输入 http://localhost:4200/#/pages/user/list/add

访问效果如下图,填入一些数据,然后点击保存,我们需要存入的数据,显示在了下方

mark

动态表单的雏形已经做出来了,现在还有几个问题

  • 样式需要优化

  • 数据如何过滤优化

  • 数据如何提交

  • 组件功能有些薄弱

这些问题我们会在后续的章节慢慢解决,可以期待。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容