[Angular 2 – Meteor]. 4. Data Binding, trói buộc dữ liệu giữa Template và code logic trong Component.

Sau khi hoàn thành bài Angular 2 Meteor 3 chúng ta đã biết được cách thức sử dụng Form trong việc quản lý dữ liệu và cả việc Routing cho phép truyền dữ liệu cho Component khác (truyền qua 1 View khác). Bài viết hôm nay chúng ta sẽ tìm hiểu về việc trói buộc dữ liệu giữa Template và code logic trong Component, để có thể tương tác Real-time hay còn gọi là Reactive.

Hãy tưởng tượng việc xây dựng 1 chương trình tính toán đơn giản trên View, nếu sử dụng Javascript và jQuery, thì chúng ta phải select từng thẻ input tương ứng, sau đó lấy giá trị trong thẻ, đổi kiểu dữ liệu từ String sang số và tính toán, sau đó lại select thẻ để hiển thị kết quả ra View. Với Angular thì chúng ta không cần phải làm như vậy, chỉ cần gắn biến trong code logic của Component với Template và mọi thứ sẽ hiển thị một cách tự động mà không cần phải select như cách cổ điển.

Từ bây giờ, chúng ta sẽ thống nhất một số thứ trước khi đi vào bài viết nha mọi người. Code logic của Component là code trong class tương ứng của Component, và chúng ta sẽ gọi nó là phần Logic, và Component có template để render View, nên chúng ta gọi nó là phần Template, như vậy mỗi Component sẽ có 2 phần: phần Logic và phần Template.

Với việc Binding giữa phần Logic và phần Template trong Component, thì chúng ta có một số khái niệm sau khi tìm hiểu, đó là:

  • Property Binding: Binding dữ liệu từ View tới các thuộc tính (property or attribute) trong thẻ html, khi có thay đổi thì nó sẽ tự động cập nhật giá trị cho thuộc tính. Có thể là những thuộc tính đã có trong html như src của thẻ img, style của thẻ bất kỳ, hoặc cũng có thẻ là những thẻ chúng ta tự viết (Directive…).
  • Event Binding: Binding sự kiện của thẻ trong HTML với hàm chức năng trong phần Logic, như các sự kiện click, keychange,…
  • Two-way Binding: Kiểu Binding cho phép bắt các thay đổi dữ liệu cho cả 2 chiều, cả từ phần Logic ra Template và ngược lại.

Chúng ta sẽ bắt đầu đi vào chi tiết của từng phần để tìm hiểu và áp dụng vào tutorial mà chúng ta đang theo dõi luôn. Về việc áp dụng thì chúng ta sẽ thực hiện binding 1 thông tin chi tiết của 1 Party, để hiển thị ra và người dùng có thể thay đổi, cập nhật lại giá trị mới.

I. Property Binding:

property-binding.png
Property Binding

Trước đây, với cú pháp {{}} trong đống syntax của Template, thì chúng ta có thể đổ dữ liệu ra View và hiển thị cho người dùng. Với Angular 1 thì khi các thay đổi dữ liệu trên View xảy ra, thì chúng ta phải gọi thêm 1 phương thức nữa đó là $scope.$apply(), để cập nhật lại dữ liệu trên View. Với Angular 2 thì không còn cần phương thức này nữa mà Angular sẽ thực hiện tạo các ChangeDetector, cho các Component tương ứng để bắt sự thay đổi. Sử dụng sức mạnh của ZoneJS, một thư viện cho phép chạy các tiến trình con trong một execution context, chi tiết hơn mình sẽ đề cập riêng về Zone sau, đại khái thì Zone kết hợp trong Angular với mục đích là bắt thay đổi dữ liệu và render lại View cho người dùng thấy được dữ liệu mới một cách nhanh nhất (Reactive).

Thì cùng với cú pháp {{}} trên, thì một loại tương tự, cũng dùng để đưa dữ liệu từ phần Logic ra phần Template đó chính là dùng Property Binding. Cú pháp của nó chính là gắn thuộc tính bằng với tên biến trong phần Logic, và chỉ cần thêm ngoặc vuông cho thuộc tính cần lấy dử liệu. Ví dụ chúng ta muốn hiển thị 1 biến username trong phần Logic ra Template, thì ở Template chúng ta có thể viết thẻ input như sau:

<input [value]="username" />

Khi đó, dữ liệu sẽ được đổ ra thuộc tính value của thẻ input trên, khi có thay đổi dữ liệu của biến username trong phần Logic, thì ở ngoài Template nó sẽ tự động cập nhật dữ liệu. Đó chính là property binding, thì ngoài thuộc tính value kể trên thì chúng ta có thể dùng với các thuộc tính khác như là: class, style.color, và một số thuộc tính đặc biệt như là disabled, hidden… thì với thuộc tính đặc biệt như disabled hoặc hidden thì nó sẽ gán với 1 biến true false, nếu true thẻ đó sẽ bị disabled và ngược lại sẽ được enable lại.

Cú pháp:

<tags [attributes]="logicVariable"></tags>

Ví dụ:

//Logic
class PropertyBindComponent {
selectedColor: String;
userLoggedIn: Boolean;

constructor () {
this.selectedColor = "red";
this.userLoggedIn = true;
}
}
<!-- Template -->

Màu chữ sẽ là màu đỏ
<h3 [hidden]="userLoggedIn">Thẻ này sẽ bị ẩn đi</h3>

Với 1 biến trong phần Logic, chúng ta sẽ thực hiện binding với thuộc tính style.color, để thể hiện màu chữ của thẻ. Đó là một chút về Property Binding, khi vào sử dụng thì các bạn sẽ gặp nhiều hơn và nó cũng không có gì là quá khó khăn cho các bạn đâu :D. Tiếp theo sẽ là Event Binding.

II. Event Binding:

event-binding.png
Event Binding

Ở phần I thì chúng ta trình bày binding dữ liệu từ phần Logic với thuộc tính trong Template, và bay giờ chúng ta sẽ làm điều "gần tương tự", đó chính là binding các sự kiện của các thẻ html trong Template để có thể gọi và thực thi các hàm chức năng (functions) trong phần Logic. Như một sự kiện click của 1 button, thì trước đây chúng ta sử dụng jQuery để bắt sự kiện cho nó, tuy nhiên trong Angular thì chúng ta chỉ cần binding đoạn code xử lý, với sự kiện trong tương ứng, thì khi sự kiện được gọi cũng là lúc đoạn code được thực thi. Ở đây mình nói chung là đoạn code xử lý là vì chúng ta có thể bỏ vào đó phương thức trong phần logic, hoặc là 1 đoạn code xử lý nho nhỏ như là 1 phép tính chẳng hạn. Các câu lệnh cũng được tách biệt với nhau dựa vào dấu chấm phẩy ;.

Cú pháp:

<tags (event)="logicMethods();executableCode;"></tags>

Ví dụ:

class EventBindComponent {
counter: Number;

constructor() {
counter = 0;
}

buttonClicked() {
console.log(counter);
}
}
<button (click)="counter=counter+1;buttonClicked();">Add new item</button>

Kết quả sẽ hiện ra mỗi khi click button thì biến counter sẽ tăng thêm 1 và sẽ show log ra. Và một điều cần phải lưu ý đó chính là các toán tử trong đoạn code thực thi trên bị giới hạn và không hỗ trợ các toán tử rút gọn như ++, –, +=, -=, …

III. Two-way Binding:

two-way-binding.png
Two-way Binding

Giả sử chúng ta muốn hiển thị giá trị của một biến trong Logic ra thẻ input, thì chúng ta sẽ sử dụng Property Binding với thuộc tính value và code như sau:

class BindingComponent {
numberOfItem: Number;
constructor() {
this.numberOfItem = 0;
}
}
<input [value]="numberOfItem" />

Tuy nhiên chúng ta lại muốn thay nhập dữ liệu từ input, và ở trong phần logic thì dữ liệu của biến cũng thay đổi theo. Chúng ta sẽ sử dụng phương pháp Binding thứ 2 đó là Event Binding với sự kiện input, khi có sự kiện input thì chúng ta gán giá trị của biến bằng với giá trị trong thẻ input mà chúng ta nhập vào, và code Template của chúng ta sẽ là:

<input [value]="numberOfItem" (input)="numberOfItem = $event.target.value" />

Thì cái $event là của Angular thôi, và chúng ta sử dụng nó để truy cập tới giá trị trong thẻ input. hoặc với những sự kiện khác thì sẽ có những thuộc tính tương ứng trong $event. Như vậy là chúng ta đã có một thuộc tính binding 2 chiều, từ Logic – Template và ngược lại.

Tuy nhiên, Angular còn có một loại Binding, cho phép thực hiện tác vụ tương tự như trên nhưng với cú pháp ngắn hơn, sử dụng thêm 1 Directive trong FormsModule đó chính là ngModel, cú pháp sử dụng nó sẽ như sau.

Cú pháp:

<input-tags [(ngModel)]="logicVariable"></input-tags>

Với Directive này thì nó sẽ tự động đồng bộ cho chúng ta mà không cần phải làm gì hơn. Và cú pháp [(ngModel)] cặp ngoặc được gọi với 1 cái tên là BananaBox, là 1 cái hộp đựng chuối, vì nó cũng giống như cái bình bỏ trái chuối vào trong. Và như vậy ví dụ chúng ta hoàn thiện sẽ là:

class BindingComponent {
numberOfItem: Number;
constructor() {
this.numberOfItem = 0;
}
}
<input [(ngModel)]="numberOfItem"/>

Và như vậy là chúng ta đã tìm hiểu xong về 3 kiểu trói buộc trong Angular, và không thể quên công việc hiện tại được, chúng ta đang follow theo tutorial, và bay giờ chúng ta sẽ áp dụng Binding vào trong tutorial.

Áp dụng Binding:

Bây giờ chúng ta sẽ áp dụng kỹ thuật Two-way binding vào, để chúng ta có thể xem trang chi tiết Party và có thể update thông tin trong trang đó. Như vậy chúng ta sẽ tùy chỉnh lại trong Component PartyDetails để thực hiện những tác vụ trên.

Hiện tại khi Route tới trang details, chúng ta sẽ có 1 object party chứa thông tin chi tiết của Party mà chúng ta cần xem, và giờ chúng ta sẽ trói buộc các thuộc tính của đối tượng này với các thẻ input bằng Two-way binding. Để khi thay đổi nội dung trong thẻ input, thì giá trị các thuộc tính của đối tượng party trong Component cũng sẽ thay đổi theo, và chúng ta sẽ lưu lại những cập nhật này.

Để cho giao diện demo nhìn đỡ classic quá thì các bạn có thể thêm package bootstrap cho meteor nhé, và chúng sẽ sử dụng bootstrap nên sẽ có thêm 1 chút class vào form :D. Cách thêm package vào meteor thì mình cũng có nhắc tới trong bài trước, các bạn có thể vào google và gõ bootstrap meteor, nó sẽ hiện cái package đó trên cùng. Cụ thể là các bạn sẽ dùng Terminal chạy dòng lệnh sau ở thư mục meteor: meteor add twbs:bootstrap, đây là 1 package bootstrap viết cho meteor và như vậy là chúng ta có thể sử dụng bootstrap.

File template (client/imports/app/parties/party-details.component.html) sẽ có một số thay đổi và sẽ có nội dung như sau:

<form *ngIf="party" (ngSubmit)="saveParty()" class="container">
<div class="form-group">
<label>Name</label>
<input type="text" [(ngModel)]="party.name" name="name" class="form-control"></div>
<div class="form-group">
<label>Description</label>
<input type="text" [(ngModel)]="party.description" name="description" class="form-control"></div>
<div class="form-group">
<label>Location</label>
<input type="text" [(ngModel)]="party.location" name="location" class="form-control"></div>
<button type="submit" class="btn btn-default">Save</button>
<a [routerLink]="['/']">Cancel</a>
</form>

Như trên là chúng ta đã trói buộc dữ liệu cho thẻ input với các thuộc tính tương ứng của đối tượng party trong phần Logic. Và chúng ta cũng có sử dụng một kỹ thuật binding nữa đó là event binding, thực hiện trói buộc sự kiện submit với phương thức saveParty() sử dụng Directive ngSubmit.

Còn lại phương thức saveParty là thứ chúng ta thiếu, bây giờ chúng ta sẽ thêm phương thức này vào phần Logic của Component. Như vậy file client/imports/app/parties/party-details.component.ts sẽ có nội dung như sau:

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

import 'rxjs/add/operator/map';

import { Parties } from '../../../../both/collections/parties.collection';
import { Party } from '../../../../both/models/party.model';

import template from './party-details.component.html';

@Component({
selector: 'party-details',
template
})
export class PartyDetailsComponent implements OnInit {
partyId: string;
party: Party;

constructor(
private route: ActivatedRoute
) {}

ngOnInit() {
console.log(this.route.params);
this.route.params
.map(params => params['partyId'])
.subscribe(partyId => {
this.partyId = partyId

this.party = Parties.findOne(this.partyId);
});
}

saveParty() {
Parties.update(this.party._id, {
$set: {
name: this.party.name,
description: this.party.description,
location: this.party.location
}
});
}
}

Start app và xem chúng ta có gì ở trang details nào :D..

details-before.png
Trước khi thực hiện thay đổi thông tin
details-after
Kết quả sau khi thực hiện thay đổi
listpage result.png
Kết quả thay đổi ở trang danh sách Party

Tổng kết:

Binding data, một khái niệm giúp ích cho chúng ta khá nhiều trong những ứng dụng đòi hỏi tương tác real-time. Thì qua bài viết này chúng ta chia sẻ với nhau thêm về 3 kiểu binding data trong Angular 2 đó là: Property Binding, Event Binding, và cuối cùng là Two-way Binding. Cùng với đó chúng ta cũng đã áp dụng vào sử dụng trong code tutorial. Mọi thứ cũng không có gì là quá khó khăn, các bạn cũng chỉ cần nắm vững ý tưởng của 3 kiểu binding này, tương tự như 3 hình ảnh mà mình để trong bài viết. 1 là từ Javascript -> HTML (Property Binding), 1 từ HTML -> Javascript (Event Binding) và cuối cùng là cả 2 (Two-way Binding).

Chúng ta cũng thấy được 2 Directive mới đó chính là ngModel và ngSubmit, thì chức năng của nó cũng được nói lên bởi cái tên rồi, tuy nhiên một thứ mình cũng muốn nói thêm là chúng ta cũng có thể tạo 1 form dạng Template Driven form để kiểm tra xem form đã submit chưa hay là submit có thành công hay không… chỉ là để cập cho các bạn quan tâm tới thôi, còn sau này khi làm với project thực hành thì chúng ta sẽ bàn chi tiết hơn :D..

Các bạn cứ comment chia sẻ nhiệt tình nhé, mình sẽ cố gắng giải đáp các thắc mắc trong tầm hiểu biết của mình, và những sai sót của mình trong bài viết cũng hy vọng được các bạn bổ sung để hoàn thiện hơn. Cảm ơn các bạn đã xem bài viết. Hẹn gặp lại các bạn trong bài viết tiếp theo trong Series Angular 2 Meteor: Xác thực người dùng và quyền riêng tư khi truy xuất dữ liệu.

Hồ Quốc Toản

Tài liệu tham khảo:

[1] How does Angular 2 Change Detection Really Work ?

[2] Course: Accelerating Through Angular 2

[3] Bindings in Angular 2 Meteor

One thought on “[Angular 2 – Meteor]. 4. Data Binding, trói buộc dữ liệu giữa Template và code logic trong Component.

  1. Pingback: [Angular 2 – Meteor]. 5. Xác thực người dùng trong Angular 2 Meteor. – Webbynat

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s