I. Mở bài – Ờ thì bài viết này nói về gì vậy?
Một ứng dụng thì luôn cần sự giao tiếp giữa các thành phần; Angular app cũng vậy, nó dùng kĩ thuật databinding để phần logic nghiệp vụ (business logic) có thể giao tiếp với phần giao diện (template) và để xử lý các giá trị động (dynamic fields) – là giá trị mà các bạn có thể thay đổi trên giao diện tùy vào code typescript của bạn. Vậy Databinding là gì?

Well,… bạn có thể hiểu nó đơn giản như sau:

DataBinding = Communication

Rõ ràng người dùng chỉ có thể thấy được tầng giao diện (template) hay nói cách khác là kết quả mà bạn xử lý bằng code, hoặc thứ gì đó bạn lấy từ server và rồi “thêm mắm thêm muối gì đó cho bớt nhạt” :V – nói chung thứ mà bạn muốn hiển thị cho người dùng. Vậy nên kĩ thuật này ra đời với thiên chức giúp cho người dùng có thể thấy được cái ta muốn “trưng” ra.

II. Phân loại các cách giao tiếp
Giao tiếp thì sẽ có hai chiều :v cuộc nói chuyện mà chỉ một chiều thì đâm ra hơi dở hơi nhỉ! Vậy ở đây có những chiều nào?

  • Output Data
  • Input Data (React to [User] Events)
  • Two-Way Binding

III. Output Data
A. String Interpolation – Cú pháp: {{ data }}
Hãy xem ví dụ sau:

<<server.component.html>>
<p>{{ 'Server' }} with ID {{ serverId }} is {{ getServerStatus() }}</p>
import { Component } from '@angular/core';
369+

@Component({
  selector: 'app-server',
  templateUrl: './server.component.html'
})
export class ServerComponent {
  serverId: number = 10;
  serverStatus: string = 'offline';

  getServerStatus() {
    return this.serverStatus;
  }
}

Okay trên là ví dụ về cách sử dụng String Interpolation. Ez game nhỉ :v

Đi sâu xíu nhen:
– Phần trong cặp dấu ngoặc nhọn (double curly braces) là một expression. Trong ví dụ trên thì expression nó tham chiếu (reference) tới là thuộc tính serverId [Đây chính là một use case bạn output giá trị của một thuộc tính ở ở bên dưới lên trên giao diện]
– Expression còn có thể chứa một hàm hoặc như ta thấy ở trên là một chuỗi gán “cứng” [Nói chung bất cứ expression nào cho vào String Interpolation sau cùng đều được trả về là một chuỗi]
– Bạn không thể cho vào nhiều expression với cùng một String Interpolation; cũng không thể bỏ các cấu trúc If hay For ở đây!

B. Property BindingCú pháp: [property]=”data”

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-servers',
  templateUrl: './servers.component.html',
  styleUrls: ['./servers.component.css']
})
export class ServersComponent implements OnInit {
  allowNewServer = false;

  constructor() {
    setTimeout(() => {
      this.allowNewServer = true;
    }, 2000);
  }

  ngOnInit() {
  }
}
  • Arrow function trong hàm setTimeout có tác dụng trả về viến allowNewServer thành True sau 2s.
  • Tiện đây mình nhắc luôn là hàm onCreateServer( ) hiện đang để default (Access modifier mà để trống không có private, public… gì hết thì là default) và default trong Angular chính là public :)))
<button
  class="btn btn-primary"
  [disabled]="!allowNewServer"
>Add Server</button>

Q: Okay, trước tiên cái tên đã nói lên được rằng trong cách giao tiếp này chúng ta muốn binding qua property. Vậy thuộc tính được nhắc tới ở đây, cụ thể là trong ví dụ này là thuộc tính nào?

A: Bạn có thể thấy trong file HTML, disable ban đầu khi chưa có dấu ngoặc vuông là một HTML boolean attribute, và sau khi HTML template được browser parse sang DOM thì nó trở thành property. Vậy thuộc tính mà chúng ta đề cập trong ví dụ này là các thuộc tính nằm trong các HTML Element (

, <p>…</p>, <table>…</table>,…)

Có một số vấn đề là bản thân một số attribute không thể set value trực tiếp trên HTML, giống như disable – bản chất cái tên của nó đã gán cho nó giá trị, bạn KHÔNG THỂ gán disable=”false” được :v Test đi sẽ biết

Và do vì lý do trên nên chúng ta phải can thiệp bằng kỹ thuật Property Binding. Nhờ bỏ vào dấu ngoặc vuông, disable sẽ từ attribute trở thành native property (Nghĩa là có thể set giá trị được)

  • Tương tự như trên trong dấu nháy đôi (quotation marks) sẽ là một expression, nhưng giờ đây, giá trị của nó không phải trả về chuỗi như trên mà sẽ được resolve(chuyển đổi) về giá trị giống với thuộc tính của property trước nó (Ở ví dụ này thì sẽ là boolean).
  • dấu ! (exclamation mark) giúp phủ định lại giá trị của nó

Bên cạnh việc dùng kỹ thuật này để binding HTML element properties thì ta còn binding các properties khác ví dụ như directives (Sẽ nói ở bài viết khác) và cả component @.@

Well, thông qua ở trên ta cũng thấy được một điểm khá là “hay hay” đấy khi dùng Angular: Easy trong việc tương tác với DOM để thay đổi cái gì đó ngay tại thời điểm runtime giống như việc ta làm ở trên (Rõ ràng disable attribute chỉ sau khi parse sang DOM mới thành property).

C. String Interpolation vs Property Binding
Trước khi qua phần Even Binding chúng ta sẽ tìm hiểu một chút về việc sử dụng hai “ông kẹ” ở trên. Cách chúng thay thế cho nhau hoặc kết hợp, cũng như một số ví dụ khác về HTML properties. Lưu ý:

Never trộn hai tên này lại với nhau. VD [disable] =”{{!allowNewServer}}”

<p>{{ allowNewServer }}</p>
<p [innerText]="allowNewServer"></p>

… Updating soon

IV. Input Data (React to [User] Events)
Nếu người dùng “vọc” gì đó trên template và chắc chắn “gì đó” ở đây phải là những hành động nằm trong kịch bản của ta (Nếu không phải mà lại vọc được thì “hệ thống” xác định ăn hành :v). Ví dụ Click vào một nút chẳng hạn, điều mà họ mong đợi là sẽ có gì đó xảy ra còn với chúng ta (dev) thì điều mong muốn là trigger một hàm nào đó bên dưới phần xử lý, lúc này ta dùng đến…

A. Event Binding – Cú pháp: (event) = “expression”

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-servers',
  templateUrl: './servers.component.html',
  styleUrls: ['./servers.component.css']
})
export class ServersComponent implements OnInit {
  allowNewServer = false;
  serverCreationStatus = 'No server was created!';
  serverName = 'Testserver';

  constructor() {
    setTimeout(() => {
      this.allowNewServer = true;
    }, 2000);
  }

  ngOnInit() {
  }

  onCreateServer() {
    this.serverCreationStatus = 'Server was created:'+ this.serverName;
  }
}
  • onCreateServer( ) bắt đầu bằng “on” nhằm đánh dấu, nhấn mạnh rằng rằng phương thức này sẽ được trigger trong template bởi User.
<button
  class="btn btn-primary"
  [disabled]="!allowNewServer"
  (click)="onCreateServer()">Add Server</button>

Khi xưa với nếu ta muốn lắng nghe click event ở HTML, ta dùng onclick=”” attribute trên HTML element (thường là với button element).
|NHƯNG với Angular ta sẽ dùng event binding (click)=””


Dưới đây là danh sách các event phổ dụng ở DOM:
(focus)=”focusCallback()”
(blur)=”blurCallback()”

(submit)=”submitCallback()”

(scroll)=”scrollCallback()”

(cut)=”cutCallback()”
(copy)=”copyCallback()”
(paste)=”pasteCallback()”

(keydown)=”keydownCallback()”
(keypress)=”keypressCallback()”
(keyup)=”keyupCallback()”

(mouseenter)=”mouseenterCallback()”
(mousedown)=”mousedownCallback()”
(mouseup)=”mouseupCallback()”

(click)=”clickCallback()”
(dblclick)=”dblclickCallback()”

(drag)=”dragCallback()”
(dragover)=”dragoverCallback()”
(drop)=”dropCallback()”

Như bạn thấy ở trên thì về cơ bản, bạn có thể binding với tất cả event tham chiếu tương đương với các HTML attribute có sẵn (chỉ cần bỏ “on” là được như onClick; onEnter; …)

Thông thường thì ta sẽ đưa hàm muốn thực thi khi sự kiện lắng nghe được trigger! Nhưng bạn hoàn toàn có thể quăng code vào trong dấu nháy kép đó.

B. One more thing…

serverName = 'Testserver';
onUpdateServerName(event: Event) {
    this.serverName = (<HTMLInputElement>event.target).value;
  }

Q: Có gì khác giữa serverName: string; và serverName = ‘ ‘;
A: Nói chung các kí pháp (notations) có một chút khác biệt”
Với “:” bạn sẽ chỉ apply kiểu dữ liệu vào biến, với “=” bạn sẽ apply giá trị. Trong trường hợp này thì TS tự iffer kiểu dữ liệu luôn nên bạn không cần phải viết serverName: string = ‘ ‘ ;

<input
  type="text"
  class="form-control"
  (input)="onUpdateServerName($event)">
<p>{{ serverName }}</p>
  • (input) là một DOM event thường dùng của input element, nó sẽ được bắt (fire) mỗi khi người dùng gõ gì đó.
  • Hàm onUpdateServerName( ) như vậy cũng sẽ được thực thi mỗi lần nhấn phím (keystroke).

Uhmmm thì rõ ràng là trường input nên cái ta cần output được những gì user nhập vào. Và chúng ta có thể làm điều đó bằng việc sử dụng $event như trên.

Q: Vậy $event là gì?…
A: Nó là một loại biến đặc biệt (reserved variable) được dùng trong event binding. Nó chứa data được emitted [Chính xác hơn là một Object trong đó có chứa trường value] với event chứa nó, nói cách khác nó giúp ta capture data và pass nó vào phương thức với như một tham số. Ví dụ: (click) event cho chúng ta một Object là MouseEvent; (input) event cho chúng ta Object InputEvent; …

Đọc thêm về reserved variable:
https://puppet.com/docs/puppet/4.9/lang_reserved.html#reserved-words

Ở ví dụ trên nếu ta console.log(event) trong hàm onUpdateServerName( ) ta sẽ thấy Object InputEvent sau:

InputEvent Object của $event trong (input)=”$event”

Như nói ở trên sự kiện này bắt được cứ mỗi lần nhấn phìm nên cứ mỗi khi nhấp vào một kí tự ta sẽ lại thấy một InputEvent Object mới xuất hiện.

Và khi nhìn và event object mà ta nhận được sẽ cho ta một vài thông tin, trong đó hãy để ý đến key “target” và mở bung target ra ta sẽ thấy “value” – ông kẹ này chính là kẻ giữ giá trị user nhập vào ở field input trên template.

… Việc còn lại thì chỉ cần lấy nó ra thôi: event.target.value

Việc chấm để gọi vào các cấp trong của một đối tượng là rất thường xuyên! Đặc biệt trong việc đọc dữ liệu từ phía database trả về dưới dạng Json. Việc các bạn cần lưu ý là phải gọi được đối tượng chứa cục json đó (Ví dụ ở đây thì đối tượng đó chính là event; từ đây thì chỉ việc chấm gọi) nêu không bạn sẽ chỉ nhận được “undified”

Ngoài ra chúng ta cũng có thể ép kiểu cho event để thông báo rằng loại của của event này là HTMLInputElement: (<HTMLInputElement>event.target)

Bạn có thể truy cập Link sau để xem các kiểu event có sẵn (Cột bên trái dưới cùng). Cơ mà cũng chẳng cần phải biết hết đâu vì có mỗi kiểu HTMLInputElement là chưa thuộc tính value:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement

Ngoài ra các bạn sẽ có thể bắt gặp thông bào rằng:

“Type assertion using the ‘<>’ syntax is forbidden. Use the ‘as’ syntax instead. (no-angle-bracket-type-assertion)tslint(1)”

Và đây là cách sửa (Thật ra để vậy cũng chả sao nhưng nếu bạn ngứa mắt với cái thông báo trên thì dùng như sau:

onUpdateServerName(event: Event) {   
    this.serverName = (event.target as HTMLInputElement).value; 
}

B. Làm cách nào để biết Properties hay Events nào của HTML Elements có thể binding?
Về cơ bản thì hầy như tất cả Properties và Events đều có thể binding được. Cơ mà nếu như muốn bạn có thể list ra được danh sách các properties và events mà element đó cung cấp

console.log( ) – Không nói ngoa khi nói rằng đây là công cụ imba nhất bạn có thể dùng để giải quyết vô số chuyện.

Cơ mà trong “họ hàng” thằng console còn có thằng console.dir( ) mà ta sẽ dùng để giải quyết vấn đề ở đây. “Phép thần chú” ở dưới này sẽ cho bạn đáp án:

var element = document.documentElement; // or any other element
var element = document.createElement("button"); // Nhiều element cần dùng cái này mới lấy ra được

console.log(element); // logs the expandable <html>…</html>
console.dir(element); // logs the element’s properties and values
Object.keys(element); // logs the element’s property names

Nếu các bạn ở ở cửa số console trên browser thì chỉ cần gõ dir(element) hoặc keys(element) – Cửa số console trên browser là cửa sổ khi bạn nhấn Ctrl+Shift+I (Thấy tab Console khống, nó đó :v nhiều bạn ban đầu chắc chỉ nghĩ nó như cửa số xem log cơ mà bạn có thể dùng nó như cmd/powershell lun á – có dấu “>” giống powershell á)

V. Two-way bidning – Món mì “trộn” đây rồi Cú pháp:[(directive)] = “data”
Dạng DataBinding kết hợp property và event binding (Nhìn nó vừa có ngoặc vuông của property vừa có ngoặc tròn của event là đủ hiểu rồi :v). Hãy xem v1 dụ bên dưới trước đã:

<input
  type="text"
  class="form-control"
  [(ngModel)]="serverName">
  • ngModel là directive mà để sử dụng bàn cần thêm FormsModule (from @angular/forms) vào imports[] ở file module.ts

Và vì đây là 2-way binding nên sẽ có đủ cả hai chiều:
+ Nó sẽ trigger input event và update giá trị của serverName ở component (trong file .ts) một cách tự động. (Event binding)
+ Nó cũng update value của input element nếu ta thay đổi giá trị serverName ở đâu đó dưới code (Property binding)

Dưới đây là ví dụ sử dụng [(ngModel)] trên select element:
https://stackblitz.com/edit/angular-7kzu8w?file=app%2Fapp.component.html

…Và với radio button + checkbox:
https://www.concretepage.com/angular-2/angular-2-radio-button-and-checkbox-example#template-set

VI. Lời kết
Bài viết cũng khá dài, mình tin sau khi theo dõi hết bài viết này các bạn cũng đủ tự tin để giải quyết các vấn đề về Data binding. Tất nhiên nếu có thắc mắc gì các bạn cứ inbox trực tiếp trên Facebook Page hoặc comment ngay dưới.
Trong tương lai có thể mình sẽ còn cập nhật thêm nội dung cho bài viết nên các bạn nhớ theo dõi nhen.

[Carry by: TungTTr –  Date: 01/04/2019]
[Update: 02/04/2019 ]