昨日我们聊了一些关於「页面」与「元件」在规划上,可能需要注意的地方。今天,我们会实际带着「页面还是元件」这样的问题意识,来实作新增英雄表单功能,在表单方面,我们将演示范本驱动的表单。
Angular 在表单方面,提供了「范本驱动」(Template driven form)及「响应式」(Reactive From)两种不同的方式。简要来说,前者多数程序会撰写在 HTML 档案、处理较简单的表单内容,後者多数程序会撰写在 TS 档案,可以更好地处理逻辑较复杂的表单。
(你终於领悟了要怎麽召唤英雄了。不过,你後面背一个大卷轴是在干嘛?)
看来你是不懂什麽叫做仙人模式啊,大闲人...话说你消失了很多天?
(哼哼...上次因为经费不足让英雄阵容缩编,让我痛定思痛...)
你真的去买狗狗币了?
(不,我发现应该要寻找廉价劳工热血的有志之士!)
听着都不合法啊。
(你知道有一种人,不怎麽厉害,但是怎麽打也打不死。然後突然就小宇宙爆发了吗?)
我好像知道你在说什麽...
(而且就纪录片来看,他们根本是弑神专门户啊!)
呃,你说的纪录片是平面的还是 3D?
图片来源:GreatGame
(特别是有个叫瞬的,只要招募到他,连他哥哥都会免费加入了,买一送一!而且他哥超厉害。)
你再说下去,我都不知道自己在睡觉还是中了什麽幻魔拳了。
(总之我们快想办法来让他们签下去吧!)
参考资料:不负责任疯动漫。《【特别加映】圣斗士星矢之五小强成长史「黄金12宫篇」》。
如同昨日的讨论,「新增英雄」功能会需要填写「英雄资料表单」,而可以想见的是,日後大概率会提供「编辑英雄」功能,而它也应该是编辑一样的「英雄资料表单」。但如果我们直接将「英雄资料表单」元件当作两个功能路径对应的页面的话,那可能会需要额外处理逻辑,幅度随两个页面在画面上的异同程度增减。
一个比较好的做法应该是,建立两个页面层级的元件「新增英雄」页面元件及「编辑英雄」页面元件,并将「英雄资料表单」作为一个共用元件,目前的专案结构规划应该是这样:
src
⌞app
⌞ pages
⌞ AddHeroPageComponent
⌞ EditHeroPageComponent
⌞ shared
⌞ components
HeroInfomationFormComponent
依序输入下列指令:
ng g c pages/add-hero-page --skip-selector // 新增英雄页面元件
ng g c pages/edit-hero-page --skip-selector // 编辑英雄页面元件
ng g c shared/components/hero-information-form // 英雄资讯表单元件
并配置对应的路由,编辑 app-routing.module.ts
,将两个页面元件配置在路径 'heroes' 下的 'add'、'edit':
const routes: Routes = [
{
path: '',
redirectTo: '/heroes',
pathMatch: 'full'
},
{
path: 'heroes',
children: [
{
path: '',
component: HeroListComponent
},
{
path: ':id',
component: HeroDetailComponent,
},
{
path: 'add',
component: AddHeroPageComponent
},
{
path: 'edit',
component: EditHeroPageComponent
}
]
},
]
这时候如果我们启动应用程序,并输入路径 http://localhost:4200/heroes/add
或 http://localhost:4200/heroes/edit
会发现画面一片空白,并且 console 都会出现 response error:
发生了什麽事呢?这是因为我们将新增的两个路由配置到参数路由 :id
之後,因此,接在 heroes
之後的 add
和 edit
均被视为 id 参数,因此导向 HeroDetailComponent 并执行取得个别英雄资料的方法 getHero(heroId),因为後台查询不到匹配这两个 id( add
、edit
) 的英雄,因此产生 Response Error。
因为一旦路由匹配成功,就不会继续往下观察路径。所以放在参数路径之後的路由都是无效的,我们应将新增的两个路由配置到参数路由之前:
const routes: Routes = [
{
path: '',
redirectTo: '/heroes',
pathMatch: 'full'
},
{
path: 'heroes',
children: [
{
path: '',
component: HeroListComponent
},
{
path: 'add',
component: AddHeroPageComponent
},
{
path: 'edit',
component: EditHeroPageComponent
},
{
path: ':id',
component: HeroDetailComponent,
},
]
},
]
如此一来,就能正常进入新配置的路由:
接着让我们来实作英雄资讯表单这个共用元件。
目前我们的英雄资料模型如下(hero.model.ts):
export interface Hero {
id?: number; // id
name: string; // 姓名
image?: string; // 图像
hp: string; // 生命值
attack: number; // 攻击力
defence: number; // 防御力
weapon?: string; // 武器
skill?: string; // 必杀技
description: string; // 人物介绍
}
这边作了稍微的调动,我们将 id 属性给为选择性的(可以不提供这个属性)。为什麽呢?这是因为大多时候,id 是由後端产生的,也就是说,在新增英雄时我们不需要传送 id 属性。
当然这可能不是一个很好的做法,也许会造成解读上的误会(原来英雄可以不用有 id?)。可以采用的方法至少有:
不过为了方便演示,目前我们先将 id 属性作为一个选填属性,将焦点放在完成表单。
首先,我们要在 AppModule 先汇入 FormsModule,这样我们才可以使用 Template-driven Form 相关的指令:
import { FormsModule } from '@angular/forms';
(略)
@NgModule({
(略)
imports: [
(略)
FormsModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
我们以 name 属性为例来讨论,在范本驱动表单的模式下,可以写为下面这样(hero-information-form.component.html
):
<div class="form-field">
<label for="name">NAME</label>
<input
#tName="ngModel"
name="name"
ngModel
required
type="text"
id="name" />
</div>
解释一下与 Angular 相关的程序码。
最核心的就是 ngModel
指令,这个指令会产生一个表单控制项(FormControl)的实例。听起来就控场控很大,没错!表单控制项就是 Angular Form 的场控基石(连欧拉夫都解不了)。表单控制项可以让我们获得该栏位的状态资讯,比较常使用到的包含:
并且会提供相应於上述表单控制状态的 class(例如不合法时提供 ng-invalid class),因此,你可以很方便地完成表单状态样式的显示。
当你使用了 ngModel 指令後,你就可以继续使用检核相关的指令,例如:
在姓名栏位,我们使用了 required 检核指令,标示这是一个必填栏位。
这样我们就完成了一个表单栏位的设定。但在画面上,我们常常需要知道检核状态,例如需要知道它是否有错误、要显示错误讯息。因此我们把这个表单控制项的实例指派给一个范本参考变数(也就是 #tName
)。如此一来,我们就可以在 HTML 档案中,以 tName 来使用表单控制项提供的各种场控技能。
例如我们新增一个「储存」按钮,并设置它在这个名称栏位不合法的时候(没有填写)是 disabeld 的:
<div class="form-field">
<label for="name">NAME</label>
<input
#tName="ngModel"
name="name"
ngModel
required
type="text"
id="name" />
</div>
<button
type="button"
[disabled]="tName.invalid">
储存
</button>
我们先在 AddHeroPageCompoent 使用这个表单元件来看看效果:
<h1>新增英雄</h1>
<app-hero-information-form></app-hero-information-form>
画面如下,在没有输入值的时候,按钮无法点击的:
当输入之後,就可以点击了:
透过范本参考变数(Template variables)#tName 和表单控制项实例(ngModel)的配合,就可以很轻松地产出动态检核栏位。明天会完成这个英雄资讯表单,并优化它的画面。
今天的程序码已推上 Github。
<<: 【Day 16】混合云 x AWS Outposts 开箱文
>>: Day 10 JavaScript CSS in JS
经历过一连串的Blue Prism实作,今天想让大家与自己都松口气, 看到上面的图案,是否有种机械...
最後,还是到了这最後一天,这第 30 天不完美的完赛,有时候时常都会想,上班就很忙了,开的 Tick...
建立结构化的 Log 系列文章 (1/4) - Elastic Common Schema 结构化 ...
在网路世界中,安全永远是最最重要的事情,而云端安全当然也不例外。任何的安全问题都来自於人为的疏忽,部...
接续上篇提到的内容,这篇提到的主要会是golang与react会需要的环境配置 小提醒 在下面会有提...