폼(Form)

기본 구성

  • 서론
  • 폼을 사용하는 이유
  • 본론
  • 템플릿 참조 변수(Template reference variable)을 이용한 사용자 입력 확인
  • NgModel Two way 바인딩을 이용한 사용자 입력
  • NgForm + NgModel을 이용한 폼 관리
  • 결론 이게 끝이 아니다. 추후에 심화로 배울것이다.

심화 구성

1. 템플릿 기반
2. 심화
3. Valitator

=======================================================================

사용자와 대화하기

지금까지는 사용자들에게 피자 목록이라는 단순하게 정보만을 제공하는 정적인 웹 문서 를 만들었다고 할 수 있습니다. 이제 정적인 웹 문서를 벗어나서 사용자들의 행동에 상호작용하는 웹 어플리케이션 으로 변화하는 과정을 알아 보도록 하겠습니다. 시대의 흐름에 따라서 사용자들이 웹 페이지에 요구하는 기능들이 많아졌습니다. 정적인 문서로 시작해서 이제는 데스크탑이나 모바일의 어플리케이션들과 같이 사용자의 입력에 반응하면서 필요한 기능들을 수행해야 합니다. 그리고 사용자들은 다양한 경험을 통해서 높은 수준의 상호작용을 기대하고 있습니다.

일반적으로 웹 페이지에서는 사용자와의 상호작용을 위하여 <input>, <select>, <textarea>태그등을 사용하여 정보들을 입력 받고 있습니다. 그리고 보통 하나의 목적을 위해서 여러 정보들을 입력받는 태그들을 하나의 <form>태그로 묶어서 관리합니다. 앵귤라도 기존의 방식처럼 여러 입력 태그들을 개별적으로 혹은 그룹을 묶어서 관리 할 수 있도록 다양한 기능들을 제공합니다.

이제부터 이러한 상호작용을 위해서 앵귤라가 지원하는 기능들을 사용하여 사용자와 대화할 수 있는 피자 주문 화면을 만들어 보겠습니다.

사용자의 입력을 확인하기

앞에서 만든 피자 주문 화면은 이미 주문 받은 정보들을 출력하는 기능만 가지고 있습니다. 여기에 사용자가 화면에서 직접 정보를 입력하고 목록에 추가하는 기능을 더해서 피자 주문 목록 확인과 주문을 동시에 할 수 있는 화면을 만들고자 합니다. 우선 기존에 만들어 놓은 템플릿에 피자 주문을 위한 내용을 추가 해보겠습니다.

<예제> src/app/orders.component.html

<ul class="orders">
    <!-- ... -->
</ul>
<form>  
  <label for="menu">Menu : </label>
  <input type="text" name="menu" id="menu"><br/>

  <label for="orderer">Orderer : </label>
  <input type="text" name="orderer" id="orderer"><br/>

  <label for="size">Size : </label>
  <select name="size" id="size">
      <option value="regular">Regular</option>
      <option value="large">Large</option>
  </select><br/>

  <input type="checkbox" name="delivery" id="delivery">
  <label for="delivery">Delivery</label><br/>

  <button type="submit">Add</button>
</form>

<그림> 피자 주문 폼

피자 주문을 위한 메뉴, 주문자, 사이즈, 배달 여부 정보를 입력해서 피자를 주문 할 수 있는 폼 영역을 추가하였습니다. 하지만 아직까지는 템플릿에 <form>, <input>, <select> 태그들을 추가했을 뿐 우리의 어플리케이션은 사용자가 어떤 정보를 입력했는지 알 수 없습니다. 이제 앵귤라가 제공하는 디렉티브나 클래스들을 사용하여 사용자가 입력한 정보가 무엇인지 확인 할 수 있도록 소스들을 수정 해보겠습니다.

<예제> src/app/app.module.ts

/* ... */
import { FormsModule } from '@angular/forms';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule
    ],
  /* ... */
})
export class AppModule { }

앵귤라는 어플리케이션과 사용자와의 상호작용을 돕기 위하여 다양한 디렉티브와 클래스들을 제공하고 있습니다. 그 중에서 기본적인 기능들을 사용하기 위해 FormsModule 에 대한 의존성을 모듈(src/app/app.module.ts)에 추가하였습니다. FormsModule 은 우리가 앞으로 사용할 ngForm, ngModel 등의 디렉티브들과 관련된 클래스들을 포함하고 있는 모듈입니다. 이제 컴포넌트 클래스와 템플릿을 수정해서 사용자의 입력에 대응할 수 있는 어플리케이션으로 진화 시켜보겠습니다.

<예제> src/app/orders.component.ts

 /* ... */
 import { NgForm } from '@angular/forms';

 /* ... */
 export class OrdersComponent {
     orders: any[] = [
         /* ... */
     ];

     addOrder(form : NgForm) {
         this.orders.push(form.value);
         form.reset();
     }
 }

컴포넌트에 메소드나 로직을 추가하기 전에 import 표현을 통해서 @angular/forms 에서 제공하는 NgForm 클래스에 대한 의존성을 추가해줍니다. NgForm 클래스는 앞으로 사용 될 ngForm 디렉티브와 깊은 연관이 있는 클래스로 <form> 태그가 관리하는 하위 입력들의 값을 참조하거나 제어하는 기능을 제공합니다. 그리고 컴포넌트 클래스에 addOrer(form : NgForm) 이라는 메소드를 추가하겠습니다. 이 메소드는 NgForm 객체를 파라미터로 받아서 이전에 정의한 주문 목록 배열인 orders에 새로운 주문 내역을 추가(this.orders.push(form.value))하고 폼의 입력값들을 초기화(form.reset())하는 역할을 할 것입니다. 앵귤라가 제공해주는 기능들을 사용해서 간단한 코드만으로 우리가 원하는 기본동작을 모두 수행할 수 있습니다. 이제 사용자들의 입력을 이용해서 addOrder 메소드를 수행시키도록 템플릿을 수정해보겠습니다.

<예제> src/app/orders.component.html

<ul class="orders">
    <!-- ... -->
</ul>
<form #form="ngForm" (ngSubmit)="addOrder(form)">
    <!-- ... -->
</form>

먼저 <form>태그에 #form="ngForm"(ngSubmit)="addOrder(form)" 이라는 표현을 추가하였습니다. 하나하나 어떤 의미를 가지고 있는지 살펴보겠습니다. #form="ngForm"템플릿 참조 변수 form 을 만들고 ngForm 디렉티브를 참조하겠다는 표현입니다. #표현은 템플릿 안에서 DOM 엘리먼트나 디렉티브를 자유롭게 사용할 수 있는 변수를 생성하는 것입니다. 이제 form 이라는 이름으로 템플릿 안에서 ngForm 디렉티브의 속성들을 사용할 수 있게 되었습니다.

그런데 여기서 한 가지 이해가 잘 안되는 곳이 있습니다. 아직 <form> 태그에 ngForm 디렉티브를 사용한 적이 없는데 브라우저가 아무런 이상 없이 잘 동작하고 있는 것입니다. 그 이유가 무엇일까요? 그것은 바로 ngForm 디렉티브가 지닌 특성 때문입니다. <form> 태그에 ngForm 을 사용하지 않겠다는 표현을 하지 않으면, <form> 태그에 자동적으로 ngForm 디렉티브가 사용됩니다. 그렇기 때문에 템플릿 참조 변수 form 으로 ngForm 디렉티브를 참조 할 수 있는 것입니다.

템플릿 참조 변수 (#변수명) 템플릿 참조 변수는 엘리먼트나 디렉티브(컴포넌트)를 참조 할 수 있게 만드는 특수한 변수입니다. 템플릿 참조 변수로 지정된 엘리먼트나 디렉티브는 같은 템플릿 내에서 지정된 이름으로 참조 할 수 있습니다. 템플릿에서만 사용할 수 있는 변수이기 때문에 컴포넌트 클래스에서는 해당 변수를 직접 사용할 수 없습니다. 태그의 속성으로 #변수명 을 추가하는 방식으로 사용할 수 있습니다. 기본적으로는 DOM 엘리먼트를 참조하기 때문에 해당 태그에 사용된 디렉티브(컴포넌트)를 참조하기 위해서는 #변수명="참조할 디렉티브 이름"으로 참조하고자 하는 디렉티브를 직접 지정해주어야 합니다. 다른 표현으로 ref-변수명 형식으로도 사용할 수 있습니다.

(ngSubmit)="addOrder(form)"ngForm 디렉티브의 ngSubmit 이벤트가 발생할 경우에 컴포넌트의 addOrder 메소드를 수행하고 파라미터로 조금 전에 선언한 템플릿 참조 변수 form 을 넘기겠다는 표현입니다. 즉, ngForm 디렉티브를 매개변수로 addOrder 메소드를 수행하는 것입니다. 그리고 이벤트의 이름에서 알 수 있듯이 ngSubmit 이벤트는 '폼의 입력을 제출한다.'는 submit 이벤트를 ngForm 디렉티브의 이벤트로 감싼 것입니다. ngForm 디렉티브가 사용된 <form> 태그에서 submit 이벤트를 대신해서 사용하는 이벤트라고 할 수 있습니다.

여기서 바인딩의 마지막 표현인 (attribute)표현이 사용되었습니다. 그 동안 {{expression}}[attribute]를 소개하였는데 이들이 뷰(템플릿)에 모델의 내용을 반영하기 위한 표현인 것과는 다르게 (attribute) 표현은 사용자의 입력과 관련있습니다. 웹에서 사용자의 모든 입력은 event 로 취급됩니다. jQuery를 다뤄보신 분들은 $(dom).on('click', clickHandler) 같은 표현에 익숙하실 것입니다. (ngSubmit)="addOrder(form)"은 예를 들자면 다음과 같은 jQuery 표현이라고 할 수 있습니다.

$(form).on('submit', function () {
    ordersComponent.addOrder(form);
});

지금까지 <form> 태그에 ngForm 디렉티브를 사용하고, 템플릿 참조 변수를 지정하여 템플릿 내에서 자유롭게 참조 할 수 있도록 해보았습니다. 이제 <form> 태그 안에 있는 태그들을 수정 해보겠습니다.

<예제> src/app/orders.component.html

<ul class="orders">
    <!-- ... -->
</ul>
<form #form="ngForm" (ngSubmit)="addOrder(form)">
  <label for="menu">Menu : </label>
  <input type="text" name="menu" id="menu" ngModel><br/>

  <label for="orderer">Orderer : </label>
  <input type="text" name="orderer" id="orderer" ngModel><br/>

  <label for="size">Size : </label>
  <select name="size" id="size" ngModel>
      <option value="regular">Regular</option>
      <option value="large">Large</option>
  </select><br/>

  <input type="checkbox" name="delivery" id="delivery" ngModel>
  <label for="delivery">Delivery</label><br/>

  <button type="submit">Add</button>
</form>

<input> 태그들과 <select> 태그에 ngModel이라는 속성을 추가하였습니다. 속성에 추가 한 ngModel은 태그들에 ngModel 디렉티브를 사용하다는 표현입니다. ngModel 디렉티브에 대해서 알아보기 전에 브라우저에서 피자 정보를 입력한 뒤 Add 버튼을 한번 클릭 해보겠습니다. 기존에 컴포넌트에서 정의한 피자 주문 정보에 브라우저에서 입력한 피자 정보가 추가된 것을 확인 할 수 있습니다.

<그림> 피자 주문 기능 동작 확인

컴포넌트 클래스와 템플릿에 많은 내용을 추가하지 않았지만 우리가 원하는 기능들이 모두 동작하고 있는 것을 확인 했습니다. 그렇다면 우리가 알게 모르게 사용한 ngFormngModel 디렉티브가 어떠한 방식으로 동작하는지 자세하게 알아보겠습니다.

ngForm
ngModel

<< ngForm과 ngModel 설명 >>

<< 템플릿에 required 추가, disabled 추가 >>

=========================================================

폼(Form)

앵귤라의 폼에 대해서 알아보자. 앵귤라의 폼은 데이터를 제어하고, 변화를 추적하고, 입력을 검증하고, 에러를 표현 할 수 있게 해줍니다.

구성

1 - 폼을 사용하는 이유 2 - 폼을 이루는 기본 요소 1 - 템플릿 기반 2 - 심화 3 - Valitator

=======================================================================

사용자와 대화하기

지금까지는 사용자들에게 피자 목록이라는 단순하게 정보만을 제공하는 정적인 웹 문서 를 만들었다고 할 수 있습니다. 이제 정적인 웹 문서를 벗어나서 사용자들의 행동에 상호작용하는 웹 어플리케이션 으로 변화하는 과정을 알아 보도록 하겠습니다. 시대의 흐름에 따라서 사용자들이 웹 페이지에 요구하는 기능들이 많아졌습니다. 정적인 문서로 시작해서 이제는 데스크탑이나 모바일의 어플리케이션들과 같이 사용자의 입력에 반응하면서 필요한 기능들을 수행해야 합니다. 그리고 사용자들은 다양한 경험을 통해서 높은 수준의 상호작용을 기대하고 있습니다. 이러한 높은 수준의 상호작용을 지원하기 위해서 앵귤라는다양한 기능들을 제공하고 있습니다.

일반적으로 웹에서 사용자의 입력을 받기 위하여 <input>, <select> 태그등을 사용하고, 이러한 입력들을 하나의 그룹으로 묶어서 관리할 수 있는 <form>태그를 같이 사용하고 있습니다. 앵귤라에서는 기존의 방식을 활용하면서 몇 가지의 편리한 기능들을 추가로 제공합니다. 앵귤라는 상호작용을 위한 기능들을 사용 목적이나 방식에 따라서 FormsModuleReactiveFormsModule 로 나누어서 제공하고 있습니다. 우선 우리는 기본적인 FormsModule 을 이용해보도록 하겠습니다. 그러면 모듈을 사용하기 위해서 src/app/app.module.ts 파일에 FormsModule 에 대한 의존성을 추가하겠습니다.

<예제> src/app/app.module.ts

/* ... */
import { FormsModule }   from '@angular/forms';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule
    ],
    /* ... */
})
export class AppModule {
    /* ... */
}

FormsModule 모듈을 추가했으니 이제 해당 모듈에서 제공하는 기능들을 사용해서 피자를 주문하는 컴포넌트를 만들고, <form>태그와 <input>태그를 이용해서 템플릿을 만들어 보겠습니다.

> ng generate component order
installing component
  create src/app/order/order.component.css
  create src/app/order/order.component.html
  create src/app/order/order.component.ts
  create src/app/order/order.component.spec
  update src/app/app.module.ts

<예제> src/app/order/order.component.ts

/* ... */
export class OrderComponent {
  /* ... */
  addOrder(formValue : any) {
      console.log("order : ", formValue);
  }
}

<예제> src/app/order/order.component.html

<form #orderForm="ngForm" (ngSubmit)="addOrder(orderForm.value)">
    <label for="menu">Menu : </label>
    <input type="text" id="menu" name="menu" required ngModel><br/>

    <label for="orderer">Orderer : </label>
    <input type="text" id="orderer" name="orderer" required ngModel><br/>

    <button type="submit">Add</button>
</form>

템플릿 소스를 살펴보면 다음과 같이 우리가 알던 표준 HTML 문법이 아닌 새로운 표현들을 확인 할 수 있습니다. 새로운 표현들에 대해서 하나씩 알아보도록 하겠습니다.

  • #orderForm="ngForm"

앵귤라 템플릿에서 #은 매우 특별한 문법입니다. 현재 템플릿 안에서 DOM 엘리먼트나 특정 디렉티브를 참조 할 수 있는 템플릿 참조 변수 를 만드는 표현입니다. (템플릿 참조 변수 에 대해서는 다른 챕터에서 좀 더 자세하게 알아보겠습니다.) 위의 표현을 해석해보자면 템플릿 안에서 NgForm 디렉티브를 orderForm이라는 변수로 참조하겠다는 것 입니다.

하지만 잘 생각해보면 우리는 템플릿에서 NgForm 디렉티브를 사용한 적이 없습니다. 사용한 적이 없는 디렉티브를 참조해서 변수를 만들었지만 에러가 발생하지 않고 있습니다. 어찌된 영문일까요? 그 이유는 바로 <form>태그 때문입니다. NgForm 디렉티브가 <form>태그를 선택자로 지정해 놓았기 때문에 NgForm 을 사용하지 않는다는 표현이 없다면, 앵귤라는 자동적으로 <form>태그에 NgForm 디렉티브를 연결합니다. NgForm 디렉티브는 form 엘리먼트에 추가적인 기능들을 제공합니다. 해당 기능들은 잠시 후 NgModel 디렉티브와 함께 살펴보도록 하겠습니다.

  • (ngSubmit)="addOrder(orderForm.value)"

앵귤라 템플릿에서 ()는 이벤트 바인딩에 사용됩니다. 참고로 웹에서 모든 입력은 이벤트로 취급됩니다. 간단하게, 디렉티브 혹은 컴포넌트의 특정 이벤트가 발생할 때 실행할 함수를 지정한다고 할 수 있습니다. (대상 이벤트)="실행 할 메소드"형식으로 이벤트 발생시 처리에 대해서 정의하는 것입니다. 위의 표현은 NgForm 디렉티브의 ngSubmit 이벤트가 발생할때 현재 컴포넌트(order)의 addOrder 메소드를 실행하고, 이때 매개변수로는 orderForm.value를 넘긴다는 의미로 볼 수 있습니다. 그리고 NgForm 디렉티브의 ngSubmit 이벤트는 form 엘리먼트의 submit 이벤트를 감싼것이기 때문에 결국 form태그의 submit 이벤트에 대한 처리를 정의한 것입니다. jQuery 를 다뤄보신 분들은 위의 표현을 다음의 소스와 같은 표현이라고 생각하시면 됩니다.

$(form).on('submit', function(event) () {
    OrderComponent.addorder(...);
});
  • ngModel

ngModelNgModel 디렉티브의 선택자로 ngModel이 명시된 태그들에는 NgModel 디렉티브가 자동으로 연결됩니다. NgModel 디렉티브는 연결된 태그들에 입력값에 대한 처리를 도와주는 특별한 객체를 생성해서 바인딩 해줍니다. 이때, 생성되는 객체의 이름은 ngModel="이름"형식으로 지정할 수 있습니다. 이름을 따로 지정하지 않는다면 태그의 name 속성값을 객체의 이름으로 사용합니다. 그렇기 때문에, 명시적으로 이름을 지정하지 않는 경우 name 속성을 반드시 지정해야합니다.

NgFormNgModel 디렉티브를 함께 사용하면 해당 <form>태그 내부에 있는 입력 태그들을 그룹으로 묶어서 한 번에 처리할 수 있습니다. NgModel 디렉티브가 사용되면 엘리먼트들의 계층 구조를 살펴서 자신의 상위에 있는 NgForm 디렉티브를 찾아서 종속 관계를 설정합니다. 우리가 작성한 템플릿을 보면 다음과 같은 관계가 자동으로 설정되는 것입니다.

NgForm
- menu (NgModel)
- orderer (NgModel)

이렇게 설정된 관계로 NgForm 이 여러 입력 태그들의 값을 한 번에 처리하거나 상태를 확인 할 수 있게 됩니다.

지금까지 앵귤라에서 제공해주는 간단한 기능들을 추가한 것으로 입력 폼을 갖는 간단한 어플리케이션을 만들 수 있었습니다. 이제 위에서 살펴본 기능들을 사용해서 피자를 주문 받는 조금 더 복잡한 어플리케이션으로 수정 해보겠습니다.

<예제> src/app/order/order.component.ts

/* ... */
import { NgForm } from '@angular/forms';

/* ... */
export class OrderComponent {
  orders : any[] = [];

  /* ... */
  addOrder(form : NgForm) {
      this.orders.push(form.value);
      form.reset();
  }
}

<예제> src/app/order/order.component.html

<ul class="orders">
    <!-- ... -->
</ul>
<form #orderForm="ngForm" (ngSubmit)="addOrder(orderForm)">
    <!-- ... -->

    <label for="size">Size : </label>
    <select name="size" id="size" ngModel>
        <option value="regular">Regular</option>
        <option value="large">Large</option>
    </select><br/>

    <input type="checkbox" name="delivery" id="delivery" ngModel>
    <label for="delivery">Delivery</label><br/>

    <!-- ... -->
</form>

NgForm, NgModel 관련 내용 추가작성 해야함!!!!!!

심화

앵귤라는 폼 입력에 대한 처리를 위하여 몇 가지 클래스를 제공합니다. 그 중에서 가장 기초적인 클래스는 FormControlFormGroup 입니다. 이 두 가지 클래스의 이름이 생소하더라도 실제로 여러분은 두 클래스를 이미 사용하고 있었습니다. 앞서 작성한 Orders 컨포넌트에서 사용한 NgForm 디렉티브와 NgMode 디렉티브는 특별한 객체를 생성해서 사용자의 입력을 관리하고 있었습니다. 그리고 우리는 그 객체들을 사용해서 사용자의 입력 값과 상태를 쉽게 확인 할 수 있었습니다. 그 특별한 객체가 바로 FormControlFormGroup 의 객체인 것입니다. 지금까지 만들어 본 간단한 형태의 폼을 관리할 때는 NgForm, NgModel 디렉티브만으로 충분했지만, 좀더 복잡한 로직을 갖는 폼 형식을 위해서는 이러한 FormControlFormGroup 같은 객체를 직접 생성하고, 관계를 설정하고, 속성들을 지정 해주어야 합니다. 이제부터 복잡한 폼 형식을 위한 클래스들과 디렉티브들을 알아보고, 직접 사용해서 우리의 피자 주문 페이지를 진화 시켜보겠습니다.

우선 여러 클래스를 직접 생성, 사용하기 위해서는 기존에 참조한 FormsModule 모듈이 아닌 ReactiveFormsModule 모듈이 필요합니다. 때문에 우선적으로 src/app/app.module.ts파일에 ReactiveFormsModule 모듈에 대한 의존성을 추가합니다.

<예제> src/app/app.module.ts

/* ... */
import { FormsModule, ReactiveFormsModule }   from '@angular/forms';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        ReactiveFormsModule
    ],
    /* ... */
})
export class AppModule {
    /* ... */
}

FormControl

FormControl 은 앵귤라에서 폼을 위해 제공하는 가장 기본적인 클래스 입니다. 하나의 FormControl 이 하나의 입력 필드를 담당하여 값을 추적하고 상태를 나타냅니다. NgModel 디렉티브를 사용하면 해당 엘리먼트 속성 값들로 초기상태를 구성하여 FormControl 객체를 한 생성해서 엘리먼트와 연결 해줍니다. 앞에서 만들었던 입력 폼에서도 사실은 FormControl을 사용해서 값을 관리하고 있던 것이죠. FormControl 객체를 직접 생성하고 사용하는 방법은 다음과 같습니다.

<예제> src/app/order/order.component.ts

// FormControl 클래스를 사용하기 위해 해당 클래스를 import 합니다.
// 앵귤라가 제공하는 기본적인 Validator를 사용하기 위해 Validators 객체를 import 합니다.
import { FormControl, Validators } from '@angular/forms';

/* ... */
export class OrderComponent {
  /* ... */

  // FormControl 객체를 생성하고 초기값 ""과 유효성 검사를 위한 Validator를 지정합니다.
  menu : FormControl = new FormControl("", Validators.required);

  // menu.value  => 값 : ''
  // menu.dirty  => 값 수정 여부 : false
  // menu.valid  => 유효성 검사 결과 : false (객체 생성시 지정한 필수값 Validator(Validators.required)를 사용해서 유효성 검사를 진행합니다.)

  // FormControl 객체를 생성하고 초기 상태와 유효성 검사를 위한 Validator를 지정합니다.
  orderer : FormControl = new FormControl({
    value : "",
    disabled : false
  }, Validators.required);

  // orderer.value  => 값 : ''
  // orderer.status => 상태 : 'INVALID'
  // orderer.valid  => 유효성 검사 결과 : false

  /* ... */
}

FormControl 객체를 생성할 때 첫 번째 파라미터는 해당 컨트롤의 초기값 혹은 초기 상태를 나타냅니다. 그리고 두번째 파라미터로 컨트롤에서 사용할 Validator를 지정할 수 있습니다. Validator가 무엇인지는 잠시 후 자세하게 알아보겠습니다. 위와 같이 FormControl 객체를 컴포넌트 클래스에서 직접 만들었다고 해서 입력 태그들에 자동적으로 연결되지는 않습니다. 템플릿에서 입력 태그가 어떠한 FormControl과 연결되는지 직접 명시해주어야 일련의 작업이 끝나게 됩니다.

<예제> src/app/order/order.component.html

<!-- ... -->
<form #orderForm="ngForm" (ngSubmit)="addOrder(orderForm.value)">
    <label for="menu">Menu : </label>
    <input type="text" id="menu" name="menu" [formControl]="menu"><br/>

    <label for="orderer">Orderer : </label>
    <input type="text" id="orderer" name="orderer" [formControl]="orderer"><br/>

    <!-- ... -->
</form>

위의 예제에서 <input>태그 마지막에 ngModel를 대신하여 [formControl]="변수명" 표현을 사용하였습니다. [formControl]은 독립형으로 상위 폼 그룹(NgForm 디렉티브 혹은 FormGroup 등 상위 객체)에 해당 컨트롤을 등록하지 않습니다. 이전에 사용했던 NgModel 디렉티브는 매우 친절하게 엘리먼트 구조를 파악하여 상위 폼 그룹에 자신을 등록했지만 [formControl]을 사용하게 되면 연관된 객체들의 관계를 직접 맺어 주어야 합니다. 이러한 과정은 FormControl들을 하나의 그룹으로 묶어서 관리할 수 있는 FormGroup 과 함께 알아보도록 하겠습니다.

FormGroup

FormGroup 은 앞에서 간략하게 소개했듯이 FromControl 들을 묶어서 하나의 그룹으로 관리할 수 있는 클래스 입니다. 비슷한 일을 수행하는 디렉티브를 사용했던 기억이 나시나요? 맞습니다, NgForm 디렉티브가 바로 FormGroup 객체를 이용해서 하위의 NgModel 디렉티브들의 FormControl 객체를 관리했던 것입니다. FormGroup 객체를 생성하고 사용하는 방법은 다음과 같습니다.

<예제> src/app/order/order.component.ts

// FormGroup 클래스를 사용하기 위해 해당 클래스를 import 합니다.
import { FormGroup, FormControl, Validators } from '@angular/forms';

/* ... */
export class OrderComponent {
  /* ... */

  form : FormGroup = new FormGroup({
    menu : new FormControl("", Validators.required),
    orderer : new FormControl("", Validators.required)
  });

  // form.value  => 값 : { menu : '', orderer : ''}
  // form.valid  => 유효성 검사 결과 : false

  /* ... */
}

위의 예제와 같이 FormGroup 객체를 생성할 때 해당 객체가 관리 할 FormControl 객체 들을 등록할 수 있습니다. 물론, 생성 이후에 FormControl 객체들을 추가하거나 제거할 수 도 있습니다. 등록된 FormControl 들은 키 값을 갖게 되며, 키 값으로 해당 컨트롤의 값을 확인하거나, 여러가지 행위들을 할 수 있습니다. 또한 FormGroup를 이용하면 관리하고 있는 FormControl들의 값이나 상태를 한 번에 확인 할 수 있습니다. 그리고 FormControl처럼 Validator를 이용해서 개별 컨트롤 뿐만 아니라 그룹 전체의 유효성 검사도 수행할 수 있습니다. 이렇게 직접 만든 FormGroup 객체를 실제 엘리먼트와 연결 해보겠습니다.

<예제> src/app/order/order.component.html

<!-- ... -->
<form [formGroup]="form" (ngSubmit)="addOrder(form.value)">
    <label for="menu">Menu : </label>
    <input type="text" id="menu" name="menu" formControlName="menu"><br/>

    <label for="orderer">Orderer : </label>
    <input type="text" id="orderer" name="orderer" formControlName="orderer"><br/>

    <!-- ... -->
</form>

이번에도 기존의 템플릿에서 바뀐 부분이 몇 군데 존재합니다. 바로 [formGroup]="form", addOrder(form.value), formControlName="menu", formControlName="orderer" 부분입니다. 먼저 [formGroup]="변수명"은 해당 엘리먼트에 특정 FormGroup 객체를 연결할 때 사용합니다. 그리고 템플릿 참조 변수 를 사용하던 addOrder(form.value)표현은 FormGroup 객체를 직접 사용하는 방식으로 변경했기 때문에 맞춰서 컴포넌트의 객체를 직접 사용하도록 수정하였습니다. 마지막으로 formControlName="Control 키" 표현은 상위 엘리먼트에 직접 연결된 FormGroup를 찾고, 그 FormGroup가 관리하는 특정 Control을 엘리먼트와 연결할때 사용합니다. 키 값을 이용해서 각각의 Control을 <input> 태그에 연결한 것입니다.

지금까지 NgFrom, NgModel 디렉티브를 이용하여 관리한 폼 입력을 컴포넌트에서 직접 관리할 수 있도록 FormGroup, FormControl 클래스 객체를 생성하고 직접 엘리먼트에 연결하는 방법까지 알아보았습니다. 하지만 이것만으로는 복잡한 로직을 갖는 사용자 입력 폼을 만들기에 부족할 것입니다. 그래서 FormGroup, FormControl 을 쉽게 생성하고 관리할 수 있는 FormBuilder 와 입력 값들에 대한 유효성 검사를 할 수 있는 Validator 에 대해서 알아보도록 하겠습니다.

FormBuilder

FormBuilder는 컴포넌트에서 직접 FormGroup과 FormControl 객체를 생성하고 관계를 설정하는 일련의 작업을 쉽게 할 수 있게 도와주는 클래스 입니다. FormBuilder는 FormControl, FormGroup, FormArray 등의 객체를 생성하는 팩토리 메소드 들을 가지고 있습니다. 이러한 메소드를 사용해서 손 쉽게 Form 관련 객체들을 생성하고 관계를 설정 할 수 있습니다. FormBuilder를 사용하여 폼 객체를 생성하는 방법은 다음과 같습니다.

<예제> src/app/order/order.component.ts

// FormBuilder 클래스를 사용하기 위해 해당 클래스를 import 합니다.
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';

/* ... */
export class OrderComponent {
  /* ... */
  form : FormGroup;

  // 생성자의 파라미터로 FormBuilder 객체를 받도록 지정하여 자동으로 FormBuilder 객체를 주입받습니다.
  constructor(private fb : FormBuilder) {
      this.form = fb.group({
        menu : ['', Validators.required],     // 초기값과 Validator를 배열로 입력받습니다.
        orderer : ['', Validators.required]
      });
  }

  /* ... */
}

기존에는 form 변수에 new 키워드를 사용하여 FormGroup 객체와 FormControl 객체들을 직접 생성하였지만 지금은 FormBuilder의 group() 메소드를 사용하여 FormGroup과 하위 FormControl 객체들을 한번에 생성하였습니다. FormBuilder는 FormGroup 객체를 생성하는 group()메소드, FormControl 객체를 생성하는 control() 메소드 그리고 FormArray 객체를 생성하는 array() 메소드를 가지고 있습니다. 이 중에서 group()array() 메소드는 예제 소스에도 볼 수 있듯이 파라미터로 입력된 정보로 FormControl 객체들을 생성하고 관계를 자동으로 설정 해줍니다.

FormBuilder는 객체안에 어떠한 정보들을 담아서 사용하는 것이 아닌 팩토리 메소드들을 갖는 클래스 입니다. 그렇기 때문에 하나의 객체를 가지고 여러 컴포넌트에서 공용으로 사용하는 것이 좋습니다. 그래서 생성자 함수의 파라미터로 FormBuilder를 넘겨받도록 설정하여 앵귤라가 관리 하고 있는 FormBuilder 객체를 주입받아서 사용하였습니다. 이러한 객체들의 의존관계 주입에 대해서는 서비스 단원 에서 자세하게 알아보겠습니다.

Validator

Validator는 FormControl이나 FormGroup의 값을 대상으로 유효성 여부를 검사하는 역할을 합니다. 이러한 Validator를 통해서 필수값 입력 이라던지 입력 데이터의 최대, 최소 길이을 확인하는 등의 데이터 검증을 수행할 수 있습니다. 그리고 앵귤라에서는 폼 입력에서 보편적으로 사용되는 유효성 검사 함수들을 제공하고 있습니다. 그것이 바로 우리가 앞서 사용한 Validators 클래스 입니다. 그렇다고 해서 유효성 검사를 하는데 있어서 꼭 Validators 클래스의 함수들만 사용해야 하는 것은 아닙니다. 입력 폼에 대한 유효성 검사는 어플리케이션마다 다를 수 있기 때문에 앵귤라에서는 직접 Validator 함수를 만들어서 FormControl이나 FormGroup에 적용할 수 있습니다. 지급부터 기본적으로 제공하는 Validator 함수들의 종류와 직접 만들어서 Form 객체들에 적용하는 방법을 알아보겠습니다.

앵귤라가 기본으로 제공하는 Validators 클래스의 함수들
  • required - 값의 존재 여부를 반환한다.
  • requiredTrue - 값의 True 여부를 반환한다.
  • minLength - 값의 최소 길이 만족 여부를 반환한다.
  • maxLength - 값의 최대 길이 만족 여부를 반환한다.
  • pattern - 값의 문자열 매칭 여부를 반환한다.
  • nullValidator - 빈 함수
  • compose - 여러개의 Validator를 하나로 묶어준다.
  • composeAsync - 여러개의 Validator를 비동기 Validator로 묶어준다.

<예제> src/app/order/order.component.ts

// FormBuilder 클래스를 사용하기 위해 해당 클래스를 import 합니다.
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';

/* ... */
export class OrderComponent {
  /* ... */
  form : FormGroup;

  // 생성자의 파라미터로 FormBuilder 객체를 받도록 지정하여 자동으로 FormBuilder 객체를 주입받습니다.
  constructor(private fb : FormBuilder) {
      this.form = fb.group({
        menu : ['', Validators.required],     // 초기값과 Validator를 배열로 입력받습니다.
        orderer : ['', Validators.required]
      });
  }

  /* ... */
}

results matching ""

    No results matching ""