# Day33 【牙起來】 - 跨元件使用表單form型別
接續上一章
今天的目標是讓IDE能夠在B元件透過`.`點點來呼風喚雨,叫出底下的物件來
要讓程式做到型別推斷

## A元件定義型別
首先我們先拿掉所有的表單驗證器
然後在`user-input.component.ts`新增介面(Interface)
```typescript=
...
export class UserInputComponent implements OnInit {
@Output() public userFormChange = new EventEmitter<FormGroup>();
myForm = this.fb.group<Player>({ // 注意這邊的寫法,`<Player>`型別是這樣加上的
name: '勇者一號',
age: 25,
})
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
this.userFormChange.emit(this.myForm);
}
formChanged() {
this.userFormChange.emit(this.myForm);
}
}
export interface Player {
name: string
age: number
}
```
這樣一來定義好表單的型別了
然後,接下來有幾種作法
## (爛)方法一:直接加上 `| undefined`
給型別後,卻出現這樣的紅色底線波浪

修改父元件`app.component.ts`
```typescript=
...
export class AppComponent {
fv: Player | undefined;
constructor() {
}
...
```
修改`calc-result.component.ts`
```typescript=
...
export class CalcResultComponent implements OnInit {
@Input() formValues: Player | undefined;
constructor() { }
...
```
然後因為`formValues`有可能為`undefined`,所以`calc-result.component.html`就要加上`?`、`!`
* `?`: 如果有`formValues`這個物件的話,再執行這段程式
* `!`: 跟編譯器說,身為工程師我比你還更了解這個型別是什麼,給我乖乖閉嘴做事
```html=
{{formValues | json}}
{{formValues?.age}}
{{formValues!.age}}
```
## 方法二:型別、初始值寫好寫滿
把`age`跟`name`都寫出來,並且給好初始值
```typescript=
...
export class AppComponent {
fv: Player = {
name: '',
age: 0,
}
constructor() {
}
...
```
> 也有冗長一點的寫法
> `new class implements ...`
>
> ```typescript=
> ...
>
> export class AppComponent {
> fv: Player = new class implements Player {
> name = '勇者是你';
> age = 25;
> };
>
> constructor() {
> }
> ...
> ```
再對`calc-result.component.ts`依樣畫葫蘆
```typescript=
import { Component, Input, OnInit } from '@angular/core';
import { Player } from '../user-input/user-input.component';
@Component({
selector: 'app-calc-result',
templateUrl: './calc-result.component.html',
styleUrls: ['./calc-result.component.css']
})
export class CalcResultComponent implements OnInit {
@Input() formValues: Player = {
name: '勇者是你',
age: 25,
}
constructor() { }
ngOnInit(): void {
}
}
```
再回到`calc-result.component.html`就可以直接使用了
```html=
{{formValues | json}}
{{formValues.age}}
```
只不過,寫初始值這件事情有點奇怪
畢竟都是接收方了,無論寫了什麼都會被蓋掉
而且滿滿的初始值宣告,當表單非常多個項目時,會讓程式碼變的無敵冗長
所以看情境使用
## 方法三:善用as技巧
用型別關鍵字`as`
> `數值 as 型別`做了什麼事情?
>
> 他打了太極拳
> 把原本不論何種型別的初始值,都化為另一種型別出去,給編譯器看
> 轉型之後,編譯器不會做檢查型別、跳提醒說屬性錯誤
修改`app.component.ts`
```typescript=
...
export class AppComponent {
fv = {} as Player;
// 這樣子寫也是同樣意思
// fv = <Player>{};
constructor() {
}
...
```
修改`calc-result.component.ts`
```typescript=
...
export class CalcResultComponent implements OnInit {
@Input() formValues = {} as Player;
// 這樣子寫也是同樣意思
// @Input() formValues = <Player>{};
constructor() { }
ngOnInit(): void {
}
...
```
太極大法好阿
## 方法四:將tsconfig修改成 不需給定初始值
就是不想給,將初始值完全省略掉
關閉ts的 **嚴格空檢查**
修改`app.component.ts`
```typescript=
...
export class AppComponent {
fv: Player;
constructor() {
}
...
```
修改`calc-result.component.ts`
```typescript=
...
export class CalcResultComponent implements OnInit {
@Input() formValues: Player;
constructor() { }
ngOnInit(): void {
}
...
```
修改`tsconfig.json`編譯設定
在`compilerOptions`底下新增`"strictPropertyInitialization": false,`

修改完後最好是關閉、重新`ng serve`一次比較不會遇到問題
---
---
---
## 加上表單驗證器的型別
新增Interface,使用`value`、`controls`並且繼承`FormGroup`
繼承`FormGroup`是為了確保可以取用`FormGroup`底下的方法
```typescript=
export class UserInputComponent implements OnInit {
@Output() public resultChanged = new EventEmitter<string>();
form = this.fb.group({
electricity: [0],
water: [0, Validators.max(9999)],
naturalGas: [{value: 0, disabled: false}, Validators.min(0)],
gas: [{value: 0, disabled: true}, Validators.min(0)],
}) as ItemGroup;
...
export interface Item {
electricity: number
water: number
naturalGas: number
gas: number
}
export interface ItemGroup extends FormGroup {
value: Item; // 型別檢查Item
controls: {
electricity: AbstractControl; // or FormControl
water: AbstractControl;
naturalGas: AbstractControl;
gas: AbstractControl;
}
}
```

而且在html可以這樣取用
// TODO 改以上範例
## 泛型
// TODO
https://ithelp.ithome.com.tw/articles/10278375
https://ithelp.ithome.com.tw/articles/10223810