2021/08/20

Form Validator In Angular

Angular 的 ReactiveFormsModule 相當強大,包含了 two way binding data 以及好用的 validator,以下的範例示範怎麼針對單一欄位以及整體表單自製 validator,如果 username 填寫 chan 的話表示帳號重複,年齡大於 100 的話則錯誤,group 的部分兩者都要填寫。

ts
import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, ValidationErrors, ValidatorFn } from '@angular/forms';
@Component({
selector: 'app-form-validator',
templateUrl: './form-validator.component.html',
styleUrls: ['./form-validator.component.scss']
})
export class FormValidatorComponent implements OnInit {
public form = this.fb.group({
username: ['', [this.userNameChecker()]],
age: ['', [this.numberChecker()]]
}, { validators: this.bothRequired() });
public ageLimit: number = 100;
constructor(
private fb: FormBuilder
) { }
ngOnInit(): void {
}
get username(): AbstractControl {
return this.form.controls.username;
}
get age(): AbstractControl {
return this.form.controls.age;
}
private userNameChecker(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (control.value === '') {
return null;
}
if (control.value === 'chan') {
return { userNameExists: true };
}
return null;
}
}
private numberChecker(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (control.value === '') {
return null;
}
if (control.value > this.ageLimit) {
return { tooBig: true };
}
return null;
}
}
private bothRequired(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
return (group.get('username')?.value === '' || group.get('age')?.value === '') ? { bothRequired: true } : null;
}
}
}
html
<h3>{{ form.value | json }}</h3>
<div [formGroup]="form">
<div>
username: <input type="text" formControlName="username">
<div *ngIf="username.errors?.userNameExists">username exists</div>
</div>
<div>
age: <input type="number" formControlName="age">
<div *ngIf="age.errors?.tooBig">age should lower than {{ ageLimit }}</div>
</div>
<div>
<button [disabled]="form.invalid">submit</button>
</div>
</div>

2021/08/13

Stop The Process By Condition In Ansible

Ansible 的任務沒有下中斷的話,即便有使用 when 偵測,playbook 會繼續往下走,可以利用 fail 這個參數讓條件不符合時直接把 process 中斷,以 stat 偵測目錄是否存在來做範例。

- hosts: localhost
vars:
path: /tmp/sync/
tasks:
- name: "stat {{ path }}"
stat:
path: "{{ path }}"
register: the_dir
- name: "shutdown when {{ path }} is not exists"
fail:
msg: "{{ path }} not exists"
when: the_dir.stat.exists == False
- name: "echo yes when {{ path }} exists"
debug:
msg: 'yes'
when: the_dir.stat.exists

當我們設定的 path 不存在時,會直接中斷,不會走道最後的 "echo yes when {{ path }} exists"

2021/08/11

Input And Output In Angular

Angular 的 @Input 以及 @Output 修飾器是用來讓父與子元件可以跨元件溝通資訊,應用範圍很廣,譬如說有些固定的表單功能我們可以把他切細,提供給各單元的表單套入使用,可以減少重複造輪子的問題,下面示範如何讓子元件改變父元件表單的內容。

父 ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.scss']
})
export class ExampleComponent implements OnInit {
public form = this.fb.group({
username: 'chan',
amount: 100
});
constructor(
private fb: FormBuilder
) { }
ngOnInit(): void {
}
public patchAmount(amount: number): void {
this.form.patchValue({
amount: amount
})
}
}
父 html
<h1>Input Output Example</h1>
<h3>{{ form.value | json }}</h3>
<form [formGroup]="form">
<div>
<input type="text" formControlName="username">
</div>
<div>
<app-example-number [amount]="form.get('amount')?.value" (patchParentAmount)="patchAmount($event)"></app-example-number>
</div>
</form>
子 ts
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-example-number',
templateUrl: './example-number.component.html',
styleUrls: ['./example-number.component.scss']
})
export class ExampleNumberComponent implements OnInit {
@Input() amount: number = 0;
@Output() patchParentAmount = new EventEmitter<number>();
constructor() { }
ngOnInit(): void {
}
public updateAmount(): void {
this.patchParentAmount.emit(this.amount);
}
}
子 html
<input type="number" [(ngModel)]="amount" (ngModelChange)="updateAmount()">

父元件傳了預設的 amount 過去給子元件,子元件透過監聽 ngModelChange 將改變的數字 emit 到父元件執行 patch form value 的動作,就可以做到互動更新了。

2021/08/09

Dynamic Form Field In Angular

Angular 的 form builder 蠻強大的,two way binding data 以及 validator 相當好用,其中有一個特性 FormArray 可以做許多運用,來做一個動態產生表單欄位的範例。

ts
import { Component, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'app-dy-demo',
templateUrl: './dy-demo.component.html',
styleUrls: ['./dy-demo.component.scss']
})
export class DyDemoComponent implements OnInit {
public form = this.fb.group({
username: 'chan',
family: this.fb.array([])
})
constructor(
private fb: FormBuilder
) { }
ngOnInit(): void {
}
public newLine(): FormGroup {
return this.fb.group({
name: '',
age: ''
})
}
public family(): FormArray {
return this.form.get('family') as FormArray;
}
public add(): void {
return this.family().push(this.newLine());
}
public remove(i: number): void {
this.family().removeAt(i);
}
}
html
<h3>{{ form.value | json }}</h3>
<div>
<button (click)="add()">add</button>
</div>
<form action="" [formGroup]="form">
<div>
<input type="text" formControlName="username">
</div>
<div formArrayName="family">
<ng-container *ngFor="let f of family().controls; let i=index" [formGroupName]="i">
<div>
username: <input type="text" formControlName="name"> age: <input type="number" formControlName="age">
<button (click)="remove(i)">remove</button>
</div>
</ng-container>
</div>
</form>

目前覺得 Angular 的內容真是易學難精啊。

2021/08/05

Array Object In Angular

angular 使用了 typescript,強型別是 ts 大殺器之一,除了簡單的 stringnumber 那些定義以外,array object 使用度也很高,最常見的作法如下。

interface Member {
username: string;
password: string;
}
const members: Member[] = [
{
username: 'chan',
password: '123456'
}
];
console.log(members);

這樣做沒什麼大問題,但設計上我覺得有點卡,因為 interface Member 就是一個單一物件,另一種寫法比較囉唆一點,但我覺得比較好:

interface Member {
username: string;
password: string;
}
interface Members extends Array<Member> {}
const members: Members = [
{
username: 'chan',
password: '123456'
}
];
console.log(members);

這樣調用 Members 時很明確就是要用陣列,而 Member 則可以給 member detail 使用,職責比較清楚,detail 理論上應該是最小單位,但如果 list 部分有自己的需求也可以自行擴充,type alias 也有相同的作法:

type Member = {
username: string;
password: string;
}
type Members = Member[];
const members: Members = [
{
username: 'chan',
password: '123456'
}
];
console.log(members);