---
# System prepended metadata

title: Day33 【牙起來】 - 跨元件使用表單form型別

---

# Day33 【牙起來】 - 跨元件使用表單form型別

接續上一章

今天的目標是讓IDE能夠在B元件透過`.`點點來呼風喚雨，叫出底下的物件來
要讓程式做到型別推斷

![](https://i.imgur.com/Wik7jTS.png)


## 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`

給型別後，卻出現這樣的紅色底線波浪

![](https://i.imgur.com/ebWMUpT.png)


修改父元件`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,`

![](https://i.imgur.com/OXCVh1a.png)

修改完後最好是關閉、重新`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;
  }
}

```

![](https://i.imgur.com/YY0g4Ic.png)

而且在html可以這樣取用

// TODO 改以上範例


## 泛型

// TODO

https://ithelp.ithome.com.tw/articles/10278375

https://ithelp.ithome.com.tw/articles/10223810