[ตอนที่ 12] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Validation ด้วย Reactive Forms

Line
Facebook
Twitter
Google

สารบัญเนื้อหา

  1. การสร้าง Reactive Forms ใน Angular
  2. การสร้าง Validation ด้วย Reactive Forms ใน Angular
  3. Custom Validators ใน Angular
  4. การสร้าง Validation Feedback
  5. ใช้งาน Reactive Forms โดยใช้ FormBuilder Service
  6. เพิ่มการรับข้อมูลใน Registration Form
  7. การจัดการเกี่ยวกับ Date ใน Forms
  8. Implement Registration Form

1. การสร้าง Reactive Forms ใน Angular

import ReactiveFormsModule จาก @angular/forms ใน app.module.ts

เพิ่ม var registerForm: FormGroup; ใน register.component.ts

registerForm: FormGroup;

เพิ่มการ initial ค่าที่ onInit() ของ register.component.ts

ngOnInit() {
  this.registerForm = new FormGroup({
    username: new FormControl(),
    password: new FormControl(),
    confirmPassword: new FormControl()
  });
}

comment register() และกำหนดให้แสดงข้อมูลใน console เมื่อกด register

register() {
  // this.authService.register(this.model).subscribe(() => {
  //   this.alertify.success('registration successfully');
  // }, error => {
  //   this.alertify.error(error);
  // });
  console.log(this.registerForm.value);
}

ปรับเปลี่ยน html form ใน register.component.html

<form [formGroup]="registerForm" (ngSubmit)="register()">
  <h2 class="text-center text-primary mt-2">Sign Up</h2>
  <hr>

  <div class="form-group">
    <input type="text" class="form-control" formControlName="username" placeholder="Username">
  </div>

  <div class="form-group">
    <input type="password" class="form-control" formControlName="password" placeholder="Password">
  </div>

  <div class="form-group">
    <input type="password" class="form-control" formControlName="confirmPassword" placeholder="Confirm Password">
  </div>

  <div class="form-group text-center">
    <button class="btn btn-success" type="submit">Register</button>
    <button class="btn btn-default" type="button" (click)="cancel()">Cancel</button>
  </div>
</form>

<p>Form value : {{registerForm.value | json}}</p>
<p>Form status : {{registerForm.status | json}}</p>

ทดสอบรัน และดูผล

2. การสร้าง Validation ด้วย Reactive Forms ใน Angular

กำหนดให้มีการ Validation ใน register.component.ts

ngOnInit() {
  this.registerForm = new FormGroup({
    username: new FormControl('Hello', Validators.required),
    password: new FormControl('', [Validators.required, Validators.minLength(6), Validators.maxLength(8)]),
    confirmPassword: new FormControl('', Validators.required)
  });
}

3. Custom Validators ใน Angular

เพิ่ม Custom Validator เป็นการตรวจสอบว่า confirm password ตรงกันหรือไม่

ngOnInit() {
  this.registerForm = new FormGroup({
    username: new FormControl('Hello', Validators.required),
    password: new FormControl('', [Validators.required, Validators.minLength(6), Validators.maxLength(8)]),
    confirmPassword: new FormControl('', Validators.required)
  }, this.passwordMatchValidator);
}

passwordMatchValidator(g: FormGroup) {
  return g.get('password').value === g.get('confirmPassword').value ? null : {'mismatch': true};
}

4. การสร้าง Validation Feedback

Validation Feedback เป็นการเพิ่มการแสดงข้อความ Error เมื่อ Form Invalid โดยเราจะทำการปรับปรุงไฟล์ register.component.html ดังนี้

<form [formGroup]="registerForm" (ngSubmit)="register()">
  <h2 class="text-center text-primary mt-2">Sign Up</h2>
  <hr>

  <div class="form-group">
    <input type="text" 
      [ngClass]="{'is-invalid': registerForm.get('username').errors && registerForm.get('username').touched}"
      class="form-control is-invalid" 
      formControlName="username" 
      placeholder="Username">
    <div class="invalid-feedback">Please choose a username</div>
  </div>

  <div class="form-group">
    <input type="password" 
      [ngClass]="{'is-invalid': registerForm.get('password').errors && registerForm.get('password').touched}"
      class="form-control" 
      formControlName="password" 
      placeholder="Password">
    <div class="invalid-feedback" *ngIf="registerForm.get('password').hasError('required') && registerForm.get('password').touched">Password is required</div>
    <div class="invalid-feedback" *ngIf="registerForm.get('password').hasError('minlength') && registerForm.get('password').touched">Password must be at least 6 charactors</div>
    <div class="invalid-feedback" *ngIf="registerForm.get('password').hasError('maxlength') && registerForm.get('password').touched">Password cannot exeed 8 charactors</div>
  </div>

  <div class="form-group">
    <input type="password" 
      [ngClass]="{'is-invalid': registerForm.get('confirmPassword').errors && registerForm.get('confirmPassword').touched || registerForm.get('confirmPassword').touched && registerForm.hasError('mismatch')}"
      class="form-control" 
      formControlName="confirmPassword" 
      placeholder="Confirm Password">
      <div class="invalid-feedback" *ngIf="registerForm.get('confirmPassword').hasError('required') && registerForm.get('confirmPassword').touched">Password is required</div>
      <div class="invalid-feedback" *ngIf="registerForm.hasError('mismatch') && registerForm.get('confirmPassword').touched">Password must match</div>
  </div>

  <div class="form-group text-center">
    <button class="btn btn-success" type="submit">Register</button>
    <button class="btn btn-default" type="button" (click)="cancel()">Cancel</button>
  </div>
</form>

<p>Form value : {{registerForm.value | json}}</p>
<p>Form status : {{registerForm.status | json}}</p>

เมื่อ Form อยู่ในสถานะ Invalid และเราได้แตะตัว input แล้ว จะมีข้อความแสดงการ Validation ออกมา

5. ใช้งาน Reactive Forms โดยใช้ FormBuilder Service

เปลี่ยนการสร้างฟอร์ม โดยเราจะสร้างฟอร์มโดยใช้ FormBuilder จะได้ชัดเจน และสะดวกขึ้น จากเดิม

ngOnInit() {
  this.registerForm = new FormGroup({
    username: new FormControl('', Validators.required),
    password: new FormControl('', [Validators.required, Validators.minLength(6), Validators.maxLength(8)]),
    confirmPassword: new FormControl('', Validators.required)
  }, this.passwordMatchValidator);
}

เป็น (เพิ่ม constructors ด้วยนะ)

private fb: FormBuilder
ngOnInit() {
  this.createRegisterForm();
}

createRegisterForm() {
  this.registerForm = this.fb.group({
    username: ['', Validators.required],
    password: ['', [Validators.required, Validators.minLength(6), Validators.maxLength(8)]],
    confirmPassword: ['', Validators.required]
  }, {validator: this.passwordMatchValidator});
}

passwordMatchValidator(g: FormGroup) {
  return g.get('password').value === g.get('confirmPassword').value ? null : {'mismatch': true};
}

ลองทดสอบดูฟอร์มต้องยังคงทำงานได้เหมือนเดิม แต่การสร้างฟอร์มจะมีรูปแบบที่ดีมากขึ้น

6. เพิ่มการรับข้อมูลใน Registration Form

เพิ่มข้อมูลที่ต้องกรอกใน RegisterFrom

createRegisterForm() {
  this.registerForm = this.fb.group({
    gender: ['male'],
    username: ['', Validators.required],
    knownAs: ['', Validators.required],
    dateOfBirth: ['', Validators.required],
    city: [null, Validators.required],
    country: ['', Validators.required],
    password: ['', [Validators.required, Validators.minLength(6), Validators.maxLength(8)]],
    confirmPassword: ['', Validators.required]
  }, {validator: this.passwordMatchValidator});
}

ปรับปรุง register.component.html

<form [formGroup]="registerForm" (ngSubmit)="register()">
  <h2 class="text-center text-primary mt-2">Sign Up</h2>
  <hr>

  <div class="form-group">
    <label class="control-label" style="margin-right:10px">I am a: </label>
    <label class="radio-inline">
      <input class="mr-3" type="radio" value="male" formControlName="gender">Male
    </label>
    <label class="radio-inline ml-3">
      <input class="mr-3" type="radio" value="female" formControlName="gender">Female
    </label>
  </div>

  <div class="form-group">
    <input type="text" 
      [ngClass]="{'is-invalid': registerForm.get('username').errors && registerForm.get('username').touched}"
      class="form-control is-invalid" 
      formControlName="username" 
      placeholder="Username">
    <div class="invalid-feedback">Please choose a username</div>
  </div>

  <div class="form-group">
    <input [ngClass]="{'is-invalid': registerForm.get('knownAs').errors && registerForm.get('knownAs').touched}" class="form-control"
      placeholder="Known as" formControlName="knownAs">
    <div class="invalid-feedback" *ngIf="registerForm.get('knownAs').touched && registerForm.get('knownAs').hasError('required')">Known as is required</div>
  </div>

  <div class="form-group">
    <input [ngClass]="{'is-invalid': registerForm.get('dateOfBirth').errors && registerForm.get('dateOfBirth').touched}" class="form-control"
      placeholder="Date of Birth" formControlName="dateOfBirth" type="date" >
    <div class="invalid-feedback" *ngIf="registerForm.get('dateOfBirth').touched && registerForm.get('dateOfBirth').hasError('required')">Date of Birth is required</div>
  </div>

  <div class="form-group">
    <input [ngClass]="{'is-invalid': registerForm.get('city').errors && registerForm.get('city').touched}" class="form-control"
      placeholder="City" formControlName="city">
    <div class="invalid-feedback" *ngIf="registerForm.get('city').touched && registerForm.get('city').hasError('required')">City is required</div>
  </div>

  <div class="form-group">
    <input [ngClass]="{'is-invalid': registerForm.get('country').errors && registerForm.get('country').touched}" class="form-control"
      placeholder="Country" formControlName="country">
    <div class="invalid-feedback" *ngIf="registerForm.get('country').touched && registerForm.get('country').hasError('required')">Country is required</div>
  </div>

  <div class="form-group">
    <input type="password" 
      [ngClass]="{'is-invalid': registerForm.get('password').errors && registerForm.get('password').touched}"
      class="form-control" 
      formControlName="password" 
      placeholder="Password">
    <div class="invalid-feedback" *ngIf="registerForm.get('password').hasError('required') && registerForm.get('password').touched">Password is required</div>
    <div class="invalid-feedback" *ngIf="registerForm.get('password').hasError('minlength') && registerForm.get('password').touched">Password must be at least 6 charactors</div>
    <div class="invalid-feedback" *ngIf="registerForm.get('password').hasError('maxlength') && registerForm.get('password').touched">Password cannot exeed 8 charactors</div>
  </div>

  <div class="form-group">
    <input type="password" 
      [ngClass]="{'is-invalid': registerForm.get('confirmPassword').errors && registerForm.get('confirmPassword').touched || registerForm.get('confirmPassword').touched && registerForm.hasError('mismatch')}"
      class="form-control" 
      formControlName="confirmPassword" 
      placeholder="Confirm Password">
      <div class="invalid-feedback" *ngIf="registerForm.get('confirmPassword').hasError('required') && registerForm.get('confirmPassword').touched">Password is required</div>
      <div class="invalid-feedback" *ngIf="registerForm.hasError('mismatch') && registerForm.get('confirmPassword').touched">Password must match</div>
  </div>

  <div class="form-group text-center">
    <button class="btn btn-success" [disabled]="!registerForm.valid" type="submit">Register</button>
    <button class="btn btn-default" type="button" (click)="cancel()">Cancel</button>
  </div>
</form>

7. การจัดการเกี่ยวกับ Date ใน Forms

เนื่องจากการ input date แบบเดิม ๆ มันธรรมดาไป เราก็เลยต้องการใช้ Datte Picker สวย ๆ แทน โดยเราจะใช้ Date Picker ที่อยู่ใน ngx-bootstrap

ทำการ import

BsDatepickerModule.forRoot()

จาก

import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';

import

BrowserAnimationsModule

จาก

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import css ใน styles.css

@import "../node_modules/ngx-bootstrap/datepicker/bs-datepicker.css";

เพิ่ม var ใน register.component.ts

bsConfig: Partial<BsDatepickerConfig>;

กำหนดค่า theme ของ date picker ใน onInit

this.bsConfig = {
  containerClass: 'theme-red'
};

เปลี่ยน input date ใน register.component.html

<input [ngClass]="{'is-invalid': registerForm.get('dateOfBirth').errors && registerForm.get('dateOfBirth').touched}" class="form-control"
      placeholder="Date of Birth" formControlName="dateOfBirth" type="text" bsDatepicker [bsConfig]="bsConfig" >

8. Implement Registration Form

ปรับปรุง API สำหรับการลงทะเบียนให้สามารถรับข้อมูลได้มากขึ้น

UserForRegisterDto.cs

using System;
using System.ComponentModel.DataAnnotations;

namespace SocialApp_API.Dtos
{
    public class UserForRegisterDto
    {
        [Required]
        public string Username { get; set; }
        [Required]
        [StringLength(8, MinimumLength = 4,ErrorMessage = "You must specify password between 4 and 8 charectors")]
        public string Password { get; set; }
        [Required]
        public string Gender { get; set; }
        [Required]
        public string KnownAs { get; set; }
        [Required]
        public DateTime DateOfBirth { get; set; }
        [Required]
        public string City { get; set; }
        [Required]
        public string Country { get; set; }
        public DateTime Created { get; set; }
        public DateTime LastActive { get; set; }
        public UserForRegisterDto()
        {
            Created = DateTime.Now;
            LastActive = DateTime.Now;
        }
    }
}

เพิ่ม AutoMapperProfiles.cs

CreateMap<UserForRegisterDto, User>();

เพิ่ม Name ใน Method GetUser() ใน Users.Controller.cs

[HttpGet("{id}", Name = "GetUser")]

ปรับปรุง Register() ใน AuthController.cs

[HttpPost("register")]
public async Task<IActionResult> Register(UserForRegisterDto userForRegisterDto)
{
    userForRegisterDto.Username = userForRegisterDto.Username.ToLower();

    if (await _repo.UserExist(userForRegisterDto.Username))
        return BadRequest("Username already exists");

    var userToCreate = _mapper.Map<User>(userForRegisterDto);

    var createdUser = await _repo.Register(userToCreate, userForRegisterDto.Password);

    var userToReturn = _mapper.Map<UserForDetailedDto>(createdUser);

    return CreatedAtRoute("GetUser", new {Controller = "User",
        id = createdUser.Id }, userToReturn);
}

ลองเรียกใช้ API ผ่าน Postman

ทำการ implement register form ใน register.component.ts

กำหนดตัวแปร

user: User;

เพิ่ม constructor

private router: Router

ปรับปรุงฟังก์ชัน register()

register() {
  if (this.registerForm.valid) {
    this.user = Object.assign({}, this.registerForm.value);
    this.authService.register(this.user).subscribe(() => {
      this.alertify.success('Registration successful');
    }, error => {
      this.alertify.error(error);
    }, () => {
      this.authService.login(this.user).subscribe(() => {
        this.router.navigate(['/members']);
      });
    });
  }
}

ปรับปรุง register() ใน auth.service.ts

register(user: User) {
  return this.http.post(this.baseUrl + 'register', user);
}

ลองทำการ Register จะพบว่า รูปภาพโปรไฟล์ไม่มี

มาแก้ปัญหากรณีภาพโปรไฟล์ไม่ขึ้นต่อ

member-card.component.html

<img src="{{user.photoUrl || '../../../../../assets/user.png'}}" alt="{{user.knownAs}}" class="card-img-top">

ทำเช่นเดียวกันใน member-edit, member-detail และ nav

ปรับปรุง initailizeUpload() ใน photo-editor.component.ts เพื่อให้เวลา upload ภาพแรกแล้วทำให้ภาพโปรไฟล์เปลี่ยนตาม

initializeUpload() {
    this.uploader = new FileUploader({
      url: this.baseUrl + 'users/' + this.authService.decodeToken.nameid + '/photos',
      authToken: 'Bearer ' + localStorage.getItem('token'),
      isHTML5: true,
      allowedFileType: ['image'],
      removeAfterUpload: true,
      autoUpload: false,
      maxFileSize: 10 * 1024 * 1024
    });

    this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; };

    this.uploader.onSuccessItem = (item, response, status, headers) => {
      if (response) {
        const res: Photo = JSON.parse(response);
        const photo = {
          id: res.id,
          url: res.url,
          dateAdded: res.dateAdded,
          description: res.description,
          isMain: res.isMain
        };
        this.photos.push(photo);
        if (photo.isMain) {
          this.authService.changeMemberPhoto(photo.url);
          this.authService.currentUser.photoUrl = photo.url;
          localStorage.setItem('user', JSON.stringify(this.authService.currentUser));
        }
      }
    };

  }

โปรดติดตามตอนต่อไป…

Line
Facebook
Twitter
Google
การติดตั้งและใช้งาน Datatables ร่วมกับ Angular
[ตอนที่ 16] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Sorting
[ตอนที่ 15] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Filtering
[ตอนที่ 14] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การใช้งาน Paging
เตรียม Atom สำหรับ React Native #3
เตรียม Visual Studio Code สำหรับ React Native #2
การติดตั้ง React Native บน macOs #1
การกำหนดค่า TF_MIN_GPU_MULTIPROCESSOR_COUNT เพื่อให้ TensorFlow ใช้งาน GPU ทุกตัว
ติดตั้ง Ubuntu 17.04 ใช้งานร่วมกับ Windows 10
การติดตั้ง TensorFlow & Caffe บน Ubuntu 16.04