[ Angular 2: Forms ] 데이터 기반의 폼 (Reactive Form)

Angular 2 에서 폼을 처리하는 방식은 템플릿 기반과 데이터 기반으로 나뉜다. 앞선 포스트에서 템플릿 기반을 다루었고, 이번 포스트는 그에 이어 데이타 기반의 폼을 살펴보려고 한다.

프로젝트 설정 부분은 이전 (템플릿 기반의 폼) 글의 초반부를 참고하자.

설정

DataDrivenComoponent 가 초기 화면에  나타날 수 있도록 selector를 포함시키자.

app.component.html

<div class="container">
  <div class="row">
    <div class="col-md-6 col-md-offset-3">
      <h1>Forms</h1>
      <app-data-driven></app-data-driven>
      <hr>
    </div>
  </div>
</div>

data-driven.component.html

<h1>Data Driven</h1>
<form>
    <div>
        <div class="form-group">
            <label for="username">Username</label>
            <input type="text"
                   class="form-control"
                   id="username">
        </div>
        <div class="form-group">
            <label for="email">E-Mail</label>
            <input type="text"
                   class="form-control"
                   id="email">
        </div>
    </div>

    <div class="form-group">
        <label for="password">Password</label>
        <input type="password"
               class="form-control"
               id="password">
    </div>
    <div class="radio">
        <label>
        </label>
    </div>
    <div>
        <h3>Hobbies</h3>
        <div class="form-group">
            <input type="text"
                   class="form-control">
        </div>
    </div>
    <button type="button" class="btn btn-default">Add Hobby</button>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

localhost:4200

Data Driven Form (데이터 기반 폼)을 사용할 때는 app.module.ts 에서 ReactiveFormsModule 을 import 에 추가해주어야한다. Reactive 는 Data-driven과 같은 의미다. 데이타 기반 폼은 다른 말로 Reactive Form 이다.

Data driven form = Reactive form = 데이타 기반 폼

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { TemplateDrivenComponent } from './template-driven/template-driven.component';
import { DataDrivenComponent } from './data-driven/data-driven.component';

@NgModule({
  declarations: [
    AppComponent,
    TemplateDrivenComponent,
    DataDrivenComponent    
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Angular 2 Form

Reactive Form을 만들때, 핵심은 FormGroupFormControl을 설정해주는 것이다. 위 HTML Form 에서 사용된 input field들을 참고하면서, 아래와 같이 FormGroup을 만들고 그 안에 FormControl을 넣어주자.

data-driven.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  constructor() { 
    this.myForm = new FormGroup({
      'username': new FormControl(),
      'email' : new FormControl(),
      'password': new FormControl()
    });
  }

  ngOnInit() {
  }

}

 Form 동기화 : HTML form 과 Angular 2 form

Html form tag 안에 formGroup 지시자를 설정해주고, ngSubmit 에벤트를 추가해준다. 템플릿 기반 폼과는 달리 ngSubmit 이벤트를 실행할 때 form 값은 넘겨주지 않는다. 또한, 각 input tag 안에 formControlName 지시자를 추가해주어야 동기화가 이루어진다.

app.component.html

<h1>Data Driven</h1>
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
    <div>
        <div class="form-group">
            <label for="username">Username</label>
            <input type="text"
                   class="form-control"
                   id="username"
                   formControlName="username">
        </div>
        <div class="form-group">
            <label for="email">E-Mail</label>
            <input type="text"
                   class="form-control"
                   id="email"
                   formControlName="email">
        </div>
    </div>

    <div class="form-group">
        <label for="password">Password</label>
        <input type="password"
               class="form-control"
               id="password"
               formControlName="password">
    </div>
    <div class="radio">
        <label>
        </label>
    </div>
    <div>
        <h3>Hobbies</h3>
        <div class="form-group">
            <input type="text"
                   class="form-control">
        </div>
    </div>
    <button type="button" class="btn btn-default">Add Hobby</button>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

DataDrivenComponent 에 onSubmit() 메서드를 추가해준다.

data-driven.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  constructor() { 
    this.myForm = new FormGroup({
      'username': new FormControl(),
      'email' : new FormControl(),
      'password': new FormControl()
    });
  }

  ngOnInit() {
  }

  onSubmit(){
    console.log(this.myForm);
  }

}

브라우저를 열고 폼에서 적당한 값을 넣고 Submit 을 클릭하면 다음과 같이 폼값이 콘솔에 표시된다.

 

데이타 유효성 검사: Validation

FormControl 의 첫번째 argument는 Default Value이고 그 다음은 Validators이다. 다음과 같이 데이타 유효성 검사를 설정해보자.

data-driven.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  constructor() { 
    this.myForm = new FormGroup({
      'username': new FormControl('John',Validators.required),
      'email' : new FormControl('',Validators.required),
      'password': new FormControl('',Validators.required)
    });
  }

  ngOnInit() {
  }

  onSubmit(){
    console.log(this.myForm);
  }

}

이제 Validators 가 작동한다. 브라우저를 열어보면, 적절한 값이 들어가지 않은 Input field에는 ng-invalid css 클래스가 자동으로 적용되어 있는 것을 확인할 수 있다.

그런데, 브라우저 창에서 폼에 잘못된 값을 입력한 채로 Submit을 실행하면, 실행이 된다. Validators 를 설정했는데, 왜 그럴까? Angular 2에서는 유효성 검사가 설정되어있지만, HTML 에서는 그렇지 않기 때문에 일단 HTML form 값의 유효성여부를 떠나서 무조건 HTML에서 Angular2로 넘어간다. 그런데, Angular 2에서는 _status가 INVALID 로 valid 프로퍼티는 false로 표시되는 것은 콘솔창에서 확인할 수 있다.

Email 유효성 검사

email 의 regex 패턴을 적용해서 email을 보다 정밀하게 검증하도록 해보자. Validators 값은 FormControl 의 두번째 argument에 단독으로 넣기도 하지만, 여러 Validators를 Array로 묶어서 넣을 수도 있다. regex pattern 을 적용하기 위해서는 Validators.pattern(“pattern here”)을 사용한다.

data-driven.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  constructor() { 
    this.myForm = new FormGroup({
      'username': new FormControl('John',Validators.required),
      'email' : new FormControl('',[Validators.required,Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]),
      'password': new FormControl('',Validators.required)
    });
  }

  ngOnInit() {
  }

  onSubmit(){
    console.log(this.myForm);
  }

}

email 패턴이 맞아야 email input field에 ng-valid 클래스가 표시되는 것을 확인할 수 있다.

Email 유효성 검사 외에도 다양한 경우가 있을 수 있으므로, Validators 에대한 Documentation 을 읽어보는 것이 도움이 된다.

FormGroup & Nested FormGroup

이제 HTML 폼이 invalid Input에 대해 제대로 반응하도록 설정해보자. 각 Input Field 아래에 *ngIf 를 사용해 데이타 유효성 검사에 따른 메시지가 실시간으로 표시되도록 했다. 또한 42번 줄에서 Submit 버튼은 myForm 이 유효할 때에만 활성화되도록 했다.

data-driven.component.html

<h1>Data Driven</h1>
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
    <div formGroupName="userData">
        <div class="form-group">
            <label for="username">Username</label>
            <input type="text"
                   class="form-control"
                   id="username"
                   formControlName="username">
                   <div *ngIf="myForm.controls['userData'].controls['username'].valid===false&&myForm.controls['userData'].controls['username'].pristine===false">Invalid Name</div>
        </div>
        <div class="form-group">
            <label for="email">E-Mail</label>
            <input type="text"
                   class="form-control"
                   id="email"
                   formControlName="email">
            <div *ngIf="myForm.controls['userData'].controls['email'].valid===false&&myForm.controls['userData'].controls['email'].pristine===false">Invalid Email</div>
        </div>
    </div>

    <div class="form-group">
        <label for="password">Password</label>
        <input type="password"
               class="form-control"
               id="password"
               formControlName="password">
               <div *ngIf="myForm.controls['password'].valid===false&&myForm.controls['password'].pristine===false">Invalid Password</div>
    </div>
    <div class="radio">
        <label>
        </label>
    </div>
    <div>
        <h3>Hobbies</h3>
        <div class="form-group">
            <input type="text"
                   class="form-control">
        </div>
    </div>
    <button type="button" class="btn btn-default">Add Hobby</button>
    <button type="submit" class="btn btn-primary" [disabled]="!myForm.valid">Submit</button>
</form>

위 3번째 줄에서 FormGroup 안에 formGroupName 지시자를 사용해 Nested FormGroup 을 만들었다. 그에 따라, ts 파일에서도 Nested FormGroup을 설정해주어야한다.

data-driven.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  constructor() {
    this.myForm = new FormGroup({
      'userData': new FormGroup({
        'username': new FormControl('John', Validators.required),
        'email': new FormControl('', [Validators.required, Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")])
      }),
      'password': new FormControl('', Validators.required)
    });
  }

  ngOnInit() {
  }

  onSubmit() {
    console.log(this.myForm);
  }

}

Nested FormGroup 을 사용했을 때, input data는 다음과 같이 그룹이 지어져 전송되게 된다. Input field 가 많을 때, 분류해서 그룹지어 사용하면 편리하다.

{
  userData: { username: 'xxx', email:'xxx'},
  password: 'xxxx'
}

Radio Button

data-driven.component.html

<h1>Data Driven</h1>
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
    <div formGroupName="userData">
        <div class="form-group">
            <label for="username">Username</label>
            <input type="text"
                   class="form-control"
                   id="username"
                   formControlName="username">
                   <div *ngIf="myForm.controls['userData'].controls['username'].valid===false&&myForm.controls['userData'].controls['username'].pristine===false">Invalid Name</div>
        </div>
        <div class="form-group">
            <label for="email">E-Mail</label>
            <input type="text"
                   class="form-control"
                   id="email"
                   formControlName="email">
            <div *ngIf="myForm.controls['userData'].controls['email'].valid===false&&myForm.controls['userData'].controls['email'].pristine===false">Invalid Email</div>
        </div>
    </div>

    <div class="form-group">
        <label for="password">Password</label>
        <input type="password"
               class="form-control"
               id="password"
               formControlName="password">
               <div *ngIf="myForm.controls['password'].valid===false&&myForm.controls['password'].pristine===false">Invalid Password</div>
    </div>
    <div class="radio" *ngFor="let g of genders">
        <label>
          <input type="radio"
                formControlName="gender"
                [value]="g">
          {{g}}
        </label>
    </div>
    <div>
        <h3>Hobbies</h3>
        <div class="form-group">
            <input type="text"
                   class="form-control">
        </div>
    </div>
    <button type="button" class="btn btn-default">Add Hobby</button>
    <button type="submit" class="btn btn-primary" [disabled]="!myForm.valid">Submit</button>
</form>

data-driven.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  genders = [
    'male',
    'female'
  ];

  constructor() {
    this.myForm = new FormGroup({
      'userData': new FormGroup({
        'username': new FormControl('John', Validators.required),
        'email': new FormControl('', [Validators.required, Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")])
      }),
      'password': new FormControl('', Validators.required),
      'gender':new FormControl('male')
    });
  }

  ngOnInit() {
  }

  onSubmit() {
    console.log(this.myForm);
  }

}

Form Array

hobbies 라는 이름 아래 여러개의 input 필드 Array 를 만들어본다. Add Hobby 버튼을 누르면 Input field 가 추가로 생성되게 한다. Submit 하면, 다음과 같이 데이타가 넘어간다.

{
  userData: {username:'xxx', email:'xxx'},
  password:'xxx',
  hobbies: ['aaa','bbb','ccc']
}

data-driven.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormArray } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  genders = [
    'male',
    'female'
  ];

  constructor() {
    this.myForm = new FormGroup({
      'userData': new FormGroup({
        'username': new FormControl('John', Validators.required),
        'email': new FormControl('', [Validators.required, Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")])
      }),
      'password': new FormControl('', Validators.required),
      'gender':new FormControl('male'),
      'hobbies': new FormArray([
        new FormControl('Cooking')
      ])
    });
  }

  ngOnInit() {
  }

  onAddHobby(){
    (<FormArray>this.myForm.controls['hobbies'])
      .push(new FormControl('',Validators.required));
  }

  onSubmit() {
    console.log(this.myForm);
  }

}

data-driven.component.html

<h1>Data Driven</h1>
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
    <div formGroupName="userData">
        <div class="form-group">
            <label for="username">Username</label>
            <input type="text"
                   class="form-control"
                   id="username"
                   formControlName="username">
                   <div *ngIf="myForm.controls['userData'].controls['username'].valid===false&&myForm.controls['userData'].controls['username'].pristine===false">Invalid Name</div>
        </div>
        <div class="form-group">
            <label for="email">E-Mail</label>
            <input type="text"
                   class="form-control"
                   id="email"
                   formControlName="email">
            <div *ngIf="myForm.controls['userData'].controls['email'].valid===false&&myForm.controls['userData'].controls['email'].pristine===false">Invalid Email</div>
        </div>
    </div>

    <div class="form-group">
        <label for="password">Password</label>
        <input type="password"
               class="form-control"
               id="password"
               formControlName="password">
               <div *ngIf="myForm.controls['password'].valid===false&&myForm.controls['password'].pristine===false">Invalid Password</div>
    </div>
    <div class="radio" *ngFor="let g of genders">
        <label>
          <input type="radio"
                formControlName="gender"
                [value]="g">
          {{g}}
        </label>
    </div>
    <div formArrayName="hobbies">
        <h3>Hobbies</h3>
        <div class="form-group" *ngFor="let hobby of myForm.controls['hobbies'].controls; let i=index">
            <input type="text"
                   class="form-control" formControlName="{{i}}">
        </div>
    </div>
    <button type="button" class="btn btn-default" (click)="onAddHobby()">Add Hobby</button>
    <button type="submit" class="btn btn-primary" [disabled]="!myForm.valid">Submit</button>
</form>

FormBuilder 사용하기

FormBuilder 는 Angular 2 form 을 조금 더 간편하게 만들수 있게 해주는 일종의 Helper이다. FormBuilder를 이용하면 form 코딩을 살짝 더 쉽게 할 수 있다. FormGroup, FormControl, FormArray 를 능숙하게 사용할 수 있고 완전히 이해하는 단계가 되면, 그때 FormBuilder 를 사용하면 좋을 것 이다.

data-driven.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormArray, FormBuilder } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  genders = [
    'male',
    'female'
  ];

  constructor(private formBuilder:FormBuilder) {
    // this.myForm = new FormGroup({
    //   'userData': new FormGroup({
    //     'username': new FormControl('John', Validators.required),
    //     'email': new FormControl('', [Validators.required, Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")])
    //   }),
    //   'password': new FormControl('', Validators.required),
    //   'gender':new FormControl('male'),
    //   'hobbies': new FormArray([
    //     new FormControl('Cooking')
    //   ])
    // });
    this.myForm = formBuilder.group({
      'userData': formBuilder.group({
        'username': ['John', Validators.required],
        'email': ['', [Validators.required, Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]]
      }),
      'password': ['', Validators.required],
      'gender':['male'],
      'hobbies': formBuilder.array([
        new FormControl('Cooking')
      ])      
    });
  }

  ngOnInit() {
  }

  onAddHobby(){
    (<FormArray>this.myForm.controls['hobbies'])
      .push(new FormControl('',Validators.required));
  }

  onSubmit() {
    console.log(this.myForm);
  }

}

 

Custom Validators

임의로 데이타 유효성 검사 로직을 만들어 사용할 수도 있다.

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormArray, FormBuilder } from "@angular/forms";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  genders = [
    'male',
    'female'
  ];

  constructor(private formBuilder: FormBuilder) {
    // this.myForm = new FormGroup({
    //   'userData': new FormGroup({
    //     'username': new FormControl('John', Validators.required),
    //     'email': new FormControl('', [Validators.required, Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")])
    //   }),
    //   'password': new FormControl('', Validators.required),
    //   'gender':new FormControl('male'),
    //   'hobbies': new FormArray([
    //     new FormControl('Cooking')
    //   ])
    // });

    //영어 알파벳.
    let REG_alpha = /^[A-Za-z]*$/;
    //영어 알파벳과 숫자.
    let REG_alphanumeric = /^[A-Za-z0-9]*$/;    
    //영어 알파벳과 숫자, 밑줄, 하이픈.
    let REG_alphanumeric_underbar_hyphen = /^[_A-Za-z0-9+]*$/;
    //영어 알파벳과 숫자 특수문자
    let REG_password = /^.*(?=.{6,20})(?=.*[0-9])(?=.*[a-zA-Z]).*$/
    //email
    let REG_email = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/

    this.myForm = formBuilder.group({
      'userData': formBuilder.group({
        'username': ['John', [Validators.required, this.exampleValidator, Validators.pattern(REG_alphanumeric)]],
        'email': ['', [Validators.required, Validators.pattern(REG_email)]]
      }),
      'password': ['', [Validators.required,Validators.pattern(REG_password)]],
      'gender': ['male'],
      'hobbies': formBuilder.array([
        new FormControl('Cooking')
      ])
    });
  }

  ngOnInit() {
  }

  onAddHobby() {
    (<FormArray>this.myForm.controls['hobbies'])
      .push(new FormControl('', Validators.required));
  }

  onSubmit() {
    console.log(this.myForm);
  }

  exampleValidator(control: FormControl): { [s: string]: boolean } {
    if (control.value === 'Example') {
      return { example: true };
    }
    return null;
  }

}

exampleValidator 가 null 값을 반환해야 유효성검사가 통과된 것이다. 따라서, control 값이 ‘Example’이면 Invalid data 가 된다.

Asynchronous Validators

데이타 베이스 액세스가 필요한 등, 유효성 검사에 있어 시간이 지체될 가능성이 있다면 Asynchronous Validators 를 사용해야한다.

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormArray, FormBuilder } from "@angular/forms";
import { Observable } from "rxjs/Observable";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  genders = [
    'male',
    'female'
  ];

  constructor(private formBuilder: FormBuilder) {
    // this.myForm = new FormGroup({
    //   'userData': new FormGroup({
    //     'username': new FormControl('John', Validators.required),
    //     'email': new FormControl('', [Validators.required, Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")])
    //   }),
    //   'password': new FormControl('', Validators.required),
    //   'gender':new FormControl('male'),
    //   'hobbies': new FormArray([
    //     new FormControl('Cooking')
    //   ])
    // });

    //영어 알파벳.
    let REG_alpha = /^[A-Za-z]*$/;
    //영어 알파벳과 숫자.
    let REG_alphanumeric = /^[A-Za-z0-9]*$/;    
    //영어 알파벳과 숫자, 밑줄, 하이픈.
    let REG_alphanumeric_underbar_hyphen = /^[_A-Za-z0-9+]*$/;
    //영어 알파벳과 숫자 특수문자
    let REG_password = /^.*(?=.{6,20})(?=.*[0-9])(?=.*[a-zA-Z]).*$/
    //email
    let REG_email = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/

    this.myForm = formBuilder.group({
      'userData': formBuilder.group({
        'username': ['John', [Validators.required, this.exampleValidator, Validators.pattern(REG_alphanumeric)]],
        'email': ['', [Validators.required, Validators.pattern(REG_email)]]
      }),
      'password': ['', [Validators.required,Validators.pattern(REG_password)]],
      'gender': ['male'],
      'hobbies': formBuilder.array([
        new FormControl('Cooking',Validators.required,this.asyncExampleValidator)
      ])
    });
  }

  ngOnInit() {
  }

  onAddHobby() {
    (<FormArray>this.myForm.controls['hobbies'])
      .push(new FormControl('', Validators.required,this.asyncExampleValidator));
  }

  onSubmit() {
    console.log(this.myForm);
  }

  exampleValidator(control: FormControl): { [s: string]: boolean } {
    if (control.value === 'Example') {
      return { example: true };
    }
    return null;
  }

  asyncExampleValidator(control:FormControl): Promise<any> | Observable<any> {
    const promise = new Promise<any>(
      (resolve,reject)=>{
        setTimeout(()=>{
          if(control.value==='Example'){
            resolve({'invalid':true});
          } else {
            resolve(null);
          }
        },1500)
      }
    );
    return promise;
  }

}

 

Status & Value Change 감지하기

validity 또는 input value 변화를 실시간으로 감지할 수 있다. 다음 예를 보자.

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormArray, FormBuilder } from "@angular/forms";
import { Observable } from "rxjs/Observable";

@Component({
  selector: 'app-data-driven',
  templateUrl: './data-driven.component.html',
  styles: []
})
export class DataDrivenComponent implements OnInit {
  myForm: FormGroup;

  genders = [
    'male',
    'female'
  ];

  constructor(private formBuilder: FormBuilder) {

    //영어 알파벳.
    let REG_alpha = /^[A-Za-z]*$/;
    //영어 알파벳과 숫자.
    let REG_alphanumeric = /^[A-Za-z0-9]*$/;
    //영어 알파벳과 숫자, 밑줄, 하이픈.
    let REG_alphanumeric_underbar_hyphen = /^[_A-Za-z0-9+]*$/;
    //영어 알파벳과 숫자 특수문자
    let REG_password = /^.*(?=.{6,20})(?=.*[0-9])(?=.*[a-zA-Z]).*$/
    //email
    let REG_email = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/

    this.myForm = formBuilder.group({
      'userData': formBuilder.group({
        'username': ['John', [Validators.required, this.exampleValidator, Validators.pattern(REG_alphanumeric)]],
        'email': ['', [Validators.required, Validators.pattern(REG_email)]]
      }),
      'password': ['', [Validators.required, Validators.pattern(REG_password)]],
      'gender': ['male'],
      'hobbies': formBuilder.array([
        new FormControl('Cooking', Validators.required, this.asyncExampleValidator)
      ])
    });
    this.myForm.valueChanges.subscribe(
      (data: any) => console.log(data)
    );
    this.myForm.statusChanges.subscribe(
      (data: any) => console.log(data)
    );
  }

  ngOnInit() {
  }

  onAddHobby() {
    (<FormArray>this.myForm.controls['hobbies'])
      .push(new FormControl('', Validators.required, this.asyncExampleValidator));
  }

  onSubmit() {
    console.log(this.myForm);
  }

  exampleValidator(control: FormControl): { [s: string]: boolean } {
    if (control.value === 'Example') {
      return { example: true };
    }
    return null;
  }

  asyncExampleValidator(control: FormControl): Promise<any> | Observable<any> {
    const promise = new Promise<any>(
      (resolve, reject) => {
        setTimeout(() => {
          if (control.value === 'Example') {
            resolve({ 'invalid': true });
          } else {
            resolve(null);
          }
        }, 1500)
      }
    );
    return promise;
  }

}

 

폼 리셋

reset() {
  this.myForm.reset(); // 비어있는 폼으로 리셋
  // this.myForm.reset({username: 'Jane', email: 'jane@example.com'}); 
 }

 

폼 값 뽑아내기

showFormValue() {
    console.log(this.myForm.value);
}

Form 에 입력된 값들이 Object 형태로 출력된다.

this.myForm.value.username 은 username 값을 담고있다.

폼 관련 참고자료

https://angular.io/docs/ts/latest/guide/forms.html

 

댓글 남기기