2018 年 6 月

第 33 卷,第 6 期

孜孜不倦的程序员 - 如何成为 MEAN:反应编程

通过Ted Neward |年 6 月 2018

我们又见面了,MEAN 用户们。

在前面的两个项目 (msdn.com/magazine/mt829391msdn.com/magazine/mt814413),我讨论大量有关角所指的"模板驱动窗体",它们是窗体由角度模板所述,然后扩展到控件验证和其他逻辑的代码使用。这是很令人满意,但有关如何查看信息"流"的新概念-和结果控件出现在代码中-在 Web 应用程序开始占用开发人员想象力。

我,当然,一的方式谈论"被动"编程和之一,将极佳之间 AngularJS (即,角度 1.0) 和角 (角度 2.0 及更) 是,角现在具有显式支持的反应样式的编程中,至少在角度级别。它们都是说,角支持而无需承诺角之外的体系结构完全反应样式使用反应角中的编程样式。

我深入了解之前,不过,它有助于确保每个人的在哪些"被动"的方式。

反应反应

从根本上说,反应的样式是编程的易于理解,只要你愿意完全反转你在执行 UI 编程的传统方法上的观点。(是,这是笑话。但仅需较小的密码。)

在传统基于 MVC 的方法中用于构建 Ui,不会创建 UI 元素 (按钮、 文本字段和 like),然后...你等待。系统处理的硬件和消息的某种组合指示用户执行了消息 — 单击按钮,键入到文本框中或任何-的代码绑定到控件激发。该代码通常修改位于在 UI 背后的模型和应用程序将返回到等待。

这种编程的"事件驱动"方式在于本质上是异步的程序员编写的代码始终由哪些用户执行驱动。在某些方面,它将是更好的做法称之为"不确定"编程,因为您曾经无法知道用户执行的操作步骤下一步。角查找若要简化此过程通过创建用户界面和数据模型之间的双向绑定,但它仍可能会很棘手,需要直接操作的顺序。

反应样式,但是,采用更单向方法。在代码中 (而不是模板中),构造控件,并以编程方式订阅发出这些控件以响应用户执行操作的事件。它不是非常响应样式反应编程,但该相当接近,并且它仍允许相同类型的"不可变数据模型"具有 enamored 行业的百分比的编程样式。

反应构造

若要开始,让我们看一下被动方式,而不模板驱动方式构造 SpeakerUI 组件。(在这里,我将使用更加传统的角度约定过头来调用它 SpeakerDetail,但名称在很大程度上无关讨论。) 首先,为了帮助简化我与正在,我将使用的缩写的形式扬声器和 SpeakerService 中, 所示图 1

图 1 扬声器和 SpeakerService

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

export class Speaker {
  id = 0
  firstName = ""
  lastName = ""
  age = 0
}

@Injectable()
export class SpeakerService {
  private speakers : Speaker[] = [
    {
      id: 1,
      firstName: 'Brian',
      lastName: 'Randell',
      age: 47,
    },
    {
      id: 2,
      firstName: 'Ted',
      lastName: 'Neward',
      age: 46,
    },
    {
      id: 3,
      firstName: 'Rachel',
      lastName: 'Appel',
      age: 39,
    }
  ]
    getSpeakers() : Promise<Array<Speaker>> {
    return Promise.resolve(this.speakers);
  }
  getSpeaker(id: number) : Promise<Speaker> {
    return Promise.resolve(this.speakers[id - 1 ]);
  }
}

请注意 SpeakerService 使用 Promise.resolve 方法以返回一个承诺,立即解决。这是模拟该服务,而无需生成与使用承诺构造函数的方式更长的承诺对象的简便方法。

接下来,SpeakerDetail 组件是只是一个标准角度组件 ("ng 新组件扬声器-详细信息"),并构造函数应注入 SpeakerService 实例作为私有构造函数参数。(这详细说明在我自 2017 年 8 月列中,"如何为平均:角度起到提取"[msdn.com/magazine/mt826349]。) 当处于它时,使用此构造函数调用 SpeakerService getSpeakers 方法以返回数组,并将其存储本地入组件作为名为"发言人。"的属性 到目前为止,这听起来很像前面所述的基于模板的组件。此组件的 HTML 模板将在系统中 (如通过 getSpeakers 获得),显示所有发言人一个下拉列表并选择了每项,然后在另一组下该下拉列表中的控件中显示详细信息。因此,模板的外观图 2

图 2 HTML 模板

<h2>Select Speaker</h2>
<form [formGroup]="selectGroup">
<label>Speaker:
  <select formControlName="selectSpeaker">
    <option *ngFor="let speaker of speakers"
            [value]="speaker.lastName">{{speaker.lastName}}</option>
  </select>
</label>
</form>

<h2>Speaker Detail</h2>
<form [formGroup]="speakerForm" novalidate>
  <div>
    <label>First Name:
      <input formControlName="firstName">
    </label>
    <label>Last Name:
      <input formControlName="lastName">
    </label>
    <label>Age:
      <input formControlName="age">
    </label>
  </div>
</form>

这可能看起来有点奇怪"基于模板的"窗体的替代方法,使用 HTML 模板。这是逻辑的很大程度上因为反应窗体方法对于模板,则不会离开此但而删除大部分离开模板和入组件。这是其中,事情开始轮流左从上一对列中检查的技术。组件代码实际上将构造一个树控件对象并与这两种形式中的控件的大多数 (如果不是所有) 交互,也会完全在基本代码。我不会将事件处理程序放在模板中。相反,我将挂钩其组件代码内。

但首先,我需要构造自己的控件。这些将是 FormControl 情况下,导入从 @angular/forms 模块,但它可以获得稍有单调乏味构造它们 (以及任何所需的验证) 的每个手动在代码中。幸运的是,角提供一个 FormBuilder 类,旨在使更简洁、 compact 构造整个窗体的积累的控件,特别是对于更长的 (或嵌套的) 窗体中的操作。

在我扬声器窗体的情况下-我想要用作所选内容机制,以选择要处理的列表中的哪个扬声器下拉列表中,我需要做些什么稍有不同。我想要具有两种形式: 一个是下拉扬声器选择控件中,和另一个包含单个扬声器的详细信息。(通常这将是在排列,主-从类中的两个不同的组件,但我尚未涵盖尚未路由)。 因此,我需要两种形式,并在图 3我显示如何构造它们,同时与 FormBuilder 和不带。

图 3 构造窗体

export class SpeakerDetailComponent implements OnInit {
  selectGroup : FormGroup
  speakerForm : FormGroup
  speakers : Speaker[]

  constructor(private formBuilder : FormBuilder,
              private speakerService : SpeakerService) {
    speakerService.getSpeakers().then( (res) => { this.speakers = res; });
    this.createForm();
  }

  createForm() {
    this.selectGroup = new FormGroup({
      selectSpeaker : new FormControl()
    });

    this.speakerForm = this.formBuilder.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      age: [0]
    });
  }
  // ...
}

这是,当然,仅类的摘录-若要添加尚未准备要运送前,,需要的一些其他内容,但此代码整齐演示两个构造窗体的方式。"选择组"是包含 HTML < 选择 > 控件中,一个 FormControl FormGroup 和 SpeakerService 用于填充扬声器实例的本地数组,因此 < 选择 > 可以填充本身。(这是实际非组件代码中的模板上。如果没有一种方法来填充组件代码从下拉列表中,我已在未找到它尚未角度 5。)

使用 FormBuilder,这是一种小少 DSL 类似语言用于构造控件填充调用 speakerForm,第二种形式。请注意如何我调用"组"以指示我正在构造的一组控件,是有效的名称值对。名称为的控件 (必须匹配每个控件的模板中的 formControlName 属性) 的名称,值为一个初始值或跟一个验证函数 (两个数组,如果你想要包括验证数组的初始值函数以异步方式运行) 运行,以验证到控件中的值用户类型。

此构造两个窗体,但在下拉列表中选择的内容不执行任何操作。若要响应事件,下拉列表中将广播,,可以执行的操作通过挂接到 FormControl,"valueChanges"方法的函数,需要如下所示:

export class SpeakerDetailComponent implements OnInit {
  // ...
  ngOnInit() {
    const speakerSelect = this.selectGroup.get('selectSpeaker');
    speakerSelect.valueChanges.forEach(
      (value:string) => {
        const speaker = this.speakers.find( (s) => s.lastName === value);
        this.populate(speaker);
      }
    );
  }
  // ...
}

请注意,valueChanges 属性实际事件的流。因此,我可以使用 forEach 以指示对于都是通过每个事件,我想控制的更改 (lambda 的参数) 的值并使用它来查找扬声器按姓氏数组中返回的 SpeakerService。然后,我将使该扬声器实例并将其传递给填充方法,此处所示:

export class SpeakerDetailComponent implements OnInit {
  // ...
  populate(speaker) {
    const firstName = this.speakerForm.get('firstName');
    const lastName = this.speakerForm.get('lastName');
    const age = this.speakerForm.get('age');
    firstName.setValue(speaker.firstName);
    lastName.setValue(speaker.lastName);
    age.setValue(speaker.age);
  }
  // ...
}

填充方法只需捕捉对 FormControls 从 speakerForm 组的引用,并使用 setValue 方法填充到相应的控件扬声器传出的讲解中的适当值。

反应分析

此时,公平要问的问题是如何,这是任何更好的基于模板的窗体,前面所示。坦白地说,它不是好的问题-它是不同的问题。(角度文档甚至说这彻底的正式文档页反应窗体上在 angular.io/guide/reactive-forms。) 捕获的大部分逻辑组件代码中 (而不是模板中) 可能 strike 某些开发人员为更逻辑或提供一致性,但反应窗体还具有的优点,它们是实质上是反应"循环":因为这些控件不直接映射到基础模型对象。开发人员将保留完全控制会发生什么情况时,这可以起到极大简化了大量方案。

角度团队不认为,窗体的一种方法优于本质上是任何其他,实际上专门指出这两种样式,可以使用相同的应用程序中。(不会尝试同时使用这两者在同一组件,但是,除非您真正深入了解角。) 此处的关键在于角支持两种不同样式的收集用户输入,并且两者都是有效且可应用于适当数量的方案。但是,如果你有大量的不同需要要显示的窗体或需要显示的窗体依赖于基础模型对象,可以更改将会发生什么情况?角提供了另一个选项,将在下一列中看到。祝您工作愉快!


Ted Neward是基于西雅图的 polytechnology 顾问、 扬声器和导师,当前正在作为的工程设计和开发人员关-程序在总监Smartsheet.com。他已写大量的文章,创作或合著十几项丛书,并于世界各地说出。可通过 ted@tedneward.com 与他联系,也可阅读他的博客 blogs.tedneward.com