Giới Thiệu Về AngularJS, AngularJS được bắt đầu từ năm 2009

I. Lịch sử và các khái niệm về AngularJS

AngularJS được bắt đầu từ năm 2009, do lập trình viên Misko Hevery tại Google viết ra như là một dự án kiểu “viết cho vui”. Misko và nhóm lúc này đang tham gia vào 1 dự án của Google tên là Google Feedback. Với AngularJS, Misko đã rút ngắn số dòng code front-end từ 17000 dòng còn chỉ khoảng 1500. Với sự thành công đó, đội ngũ của dự án Google Feedback quyết định phát triển AngularJS theo hướng mã nguồn mở. Theo thông số từ Github mà mình thấy, hiện tại dự án AngularJS đang có gần 11000 người theo dõi và hơn 2000 lượt fork.

Công nghệ HTML hỗ trợ tốt cho các trang web tĩnh, kiểu như trước năm 2000 vậy. Khi bạn xây dựng 1 trang web với PHP, Node/Express, hay Ruby thì nó cũng chỉ là một trang web tĩnh với nội dung được thay đổi khi bạn gửi request về máy chủ, máy chủ sẽ render 1 trang với nội dung tương ứng. Tuy nhiên mọi thứ đã thay đổi nhiều từ sự phát triển của HTML5, nhất là khi có sự chống lưng từ những ông lớn như Google, Yahoo, Facebook, và sự tập hợp đông đảo của cộng đồng mã nguồn mở.

Douglas Crockford, người được biết đến nhiều qua JSON và JSLint đã dùng sự chênh lệch về độ dày giữa 2 cuốn sách “Javascript: The definitive guide” và “Javascript: The good parts” để châm biếm 1 cách hài hước về JavaScript.

[IMG]

Tuy nhiên, sự thành công của jQuery đã khiến JavaScript được nhiều người yêu thích vì tính đơn giản và dễ sử dụng. Việc phát triển 1 website sử dụng AJAX thì không khó, bạn có thể dùng jQuery để làm việc này với $.ajax tuy nhiên làm thế nào để xây dựng một phần mềm có thể mở rộng, dễ test, nâng cấp và bảo trì thì không hề đơn giản vì bản thân JavaScript không được thiết kế ngay từ đầu để làm những việc này. Do đó sự ra đời của những framework hỗ trợ lập trình viên xây dựng ứng dụng web 1 cách có hệ thống đã ra đời như Sencha (cái này thì phải trả phí), Ember, Knockout, Backbone, và AngularJS.

Hello world, tại sao lại không nhỉ?

Chắc các bạn đang nóng lòng muốn viết thử xem cái Hello World của AngularJS lắm, thế thì tại sao lại không làm luôn nhỉ

Để bắt đầu các bạn hãy tạo thư mục theo cấu trúc sau

Dưới đây là source code mẫu cho index.html, chúng ta sẽ dùng AngularJS từ CDN

<html>

 <head>

 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

 <script src="scripts/controllers.js"></script>

 </head>

 <body ng-app>

 <div ng-controller="HelloCtrl">

 Ban ten la: <input type="text" ng-model="name"/>

 <p>Xin chao ban <b>{{ name }}</b> ^_^</p>

 </div>

 </body>

 </html>

Bây giờ tới controllers.js

function HelloCtrl($scope) {

$scope.name = "bi an";

}

Bạn có thể thấy trong source code mình không hề tạo ra một eventListener nào cho cái input cả, nhưng khi bạn thay đổi nội dung input, lời chào sẽ tự động cập nhật. Điểm mấu chốt ở đây là data-binding, mình sẽ gỡ cọc sau.

Q&A:

Q: ng-app là gì vậy?
A: ng-app là nơi bạn muốn đánh dấu cho AngularJS biết ứng dụng sẽ bắt đầu từ đâu, thường thì nó nằm trong body tag, nhưng bạn có thể để nó trong div cũng không sao

Q: thế còn ng-controller là gì?
A: AngularJS dùng mô hình MVC, mỗi controller sẽ có nhiệm vụ quản lý những model bên trong nó, ví dụ như name vậy.

Q: $scope tại sao lại có $, có liên quan gì tới jQuery không?
A: Thực ra thì chẳng có liên quan gì tới jQuery ở đây. Kí hiệu $ được dùng để phân biệt thành phần do AngularJS tạo ra, như ng- namespace vậy. Việc $scope đc đưa vào controller là do dependency injection. Khi bạn dùng AngularJS với jQuery, $ sẽ được wrap bằng angular.element.

Q: Tại sao lại có ng- prefix?
A: Tại nó phát âm giống angular thôi, tác giả dùng nó như là namespace cho AngularJS. Để chuẩn hoá HTML template, bạn có thể thêm prefix data thành data-ng-. Mọi thứ sẽ vẫn hoạt động tốt

Mô hình AngularJS MVC

[IMG]

Mô hình MVC hay Model-View-Controller là một trong những kiến trúc phần mềm bạn sẽ được nghe nói tới từ lập trình web, đtdđ, hay cả ứng dụng trên desktop nữa. Thật ra MVC đã có từ rất lâu rồi, nó được giới thiệu tận những năm 70 như là 1 phần của Smalltalk, nhưng đối với nền tảng web, thì nó mới được thịnh hành gần đây.

Ý tưởng đằng sau MVC là để chia rõ 3 thành phần chính là model(data của bạn), view(là giao diện), và controller(phần xử lý logic). Nếu các bạn là dân PHP thì có thể đoạn code dưới dây sẽ nhìn rất là quen thuộc

<?php if ($role === 'admin') {?>

<div><?="Xin chao admin"?></div>

<?php} else {?>

<div><?="Chao user"?></div>

<?php } ?>

Khi ứng dụng trở nên phức tạp, việc bảo trì, sửa lỗi, và nâng cấp sẽ trở nên cực kỳ khó khăn khi mà logic và ui đan xen lẫn nhau như trên.

Đối với Angular, view sẽ là DOM, controller là các lớp JavaScript, còn model sẽ là dữ liệu được lưu ở thuộc tính của các đối tượng trong JS.

II. Data-binding trong AngularJS

Rất đơn giản, AngularJS sử dụng cú pháp: {{data}} để hiển thị giá trị của data trong DOM. Điều thú vị ở đây là kết quả mà data bạn cập nhật sẽ được hiển thị ngay tại chỗ chứ không cần thiết phải reload page:

Ví dụ:

<div class='container' ng-app>

 <input type='text' ng-model='userName'/>

 <p>Hello {{userName}}!</p>

</div>

Khi bạn nhập tên trong input text, bạn sẽ thấy nội dung trong thẻ <p> được cập nhật ngay lập tức.

Một ví dụ nữa về khả năng này của AngularJS:

<div class='container' ng-app>

 <div ng-controller='myController'>

 <form ng-submit='addNewUser()'>

 <input type='text' ng-model='NewUser'/>

 <input type="submit"/>

 </form>

 <p>Hello {{NewUser}}!</p>

 <p>User Array: {{userList}}</p>
var myController = function($scope){

$scope.userList = ['Rikky'];</code></code>$scope.addNewUser = function(){

$scope.userList.push($scope.NewUser);

};

}

Mỗi lần bạn submit một user mới, user đó sẽ được push vào mảng userList. Thú vị là mọi sự thay đổi từ controller, ngay lập tức được DOM cập nhật, và ngược lại. Điều này được AngularJS đề cập như là một “dữ liệu chân lý duy nhất” (single-source-of-truth).

Khi bạn làm việc với các mô hình MVC framework khác:

[IMG]

Bạn sẽ rơi vào trường hợp model và view chỉ được ghép với nhau một lần duy nhất, vì vậy mọi sự cập nhật dữ liệu từ một trong hai lớp đó đều không liên quan gì đến lớp còn lại. Hệ quả là developer phải viết thêm phần “đồng bộ hóa” giữa hai lớp này.

Với AngularJS thì khác:

[IMG]

Quá trình đồng bộ luôn xảy ra giữa model và view. Mọi sự thay đổi dữ liệu trong lớp này, ngay lập tức được cập nhật vào lớp kia. Bạn chỉ cần “bind” một lần, và mọi thứ hoạt động trơn tru .

Cũng vì dữ liệu mà View hiển thị thực chất là sự ánh xạ từ Model, nên Controller cũng hoàn toàn không cần các thao tác data binding phức tạp và phụ thuộc vào cấu trúc DOM của View, kết quả là Controller của bạn sẽ không quan tâm xem View của bạn là gì. Và dĩ nhiên, logic giữa Controller và View được bóc tách rất rõ ràng. Code của bạn sẽ rất dễ để debug, viết unit-test. Còn gì hay ho hơn!
.
.
.
.
.
Thực ra là còn . Hãy tưởng tượng sự kết hợp hoàn hảo giữa AngularJS với web-socket, mọi thứ được cập nhật real time. Một thao tác cập nhật ở server xảy ra, ngay tức thì, nó sẽ được AngularJS ghi nhận và cập nhật lại Model của mình. Tiếp tục, đến lượt View, nó cũng sẽ cập nhật lại dữ liệu hiển thị của nó theo Model  Welcome to real-time world!
Ví dụ:

Ở đây mình dùng FireBase làm cơ sở dữ liệu. FireBase là một cơ sở dữ liệu online, realtime. Nó giúp bạn đọc, ghi dữ liệu trực tuyến:

<div class='container' ng-app>

 <div ng-controller='myController'>

 <div>

 <div>Current message is:</div>

 <br/>

 <p class='well well-small'>{{msg}}</p>

 </div>

 <hr/>

 <form ng-submit="updateMsg()">

 <input type="text" ng-model='newMsg' placeholder='Type your message'/>

 <input type="submit" class='btn btn-primary pull-right'/>

 </form>

var myController = function($scope){

 $scope.msg = 'Loading...';

 var messageRef = new Firebase('https://rikkydb.firebaseio.com/AngularDemoDb/message/');

 messageRef.on('value', function(snapshot){

 $scope.msg = snapshot.val();

 $scope.$apply(); // Thắc mắc chỗ này ư? Mình sẽ giải thích ngay sau đây!

 });

$scope.updateMsg = function(){

 messageRef.set($scope.newMsg);

 }};

Bạn thử gõ một message mới, ngay tức thì dữ liệu sẽ được cập nhật.
(Nhớ đừng gõ cái gì linh tinh nhé )

Outer data-binding
Trước hết, bạn hãy thử giải quyết bài toán sau đây:

“Tạo ra một đồng hồ đếm từ 1 đến 10 bằng AngularJS rồi lại quay về 1 và cứ thế tiếp tục?”

Dễ ợt: Bạn tạo một Model timer và dùng hàm setInterval để đếm đến 10, nếu đã bằng 10, set lại timer bằng 1.

Okay, đây là code:

<div class='container' ng-app>

 <div ng-controller='myController'>

 <h2>{{timer}}</h2>

 <button class='btn btn-primary' ng-click='count10()'>Start count to 10</button>

</div>

</div>
 
var myController = function ($scope) {

$scope.timer = 1;

$scope.count10 = function () {

setInterval(function () {

if (10 > $scope.timer) {

$scope.timer++;

} else {

$scope.timer = 1;

}

}, 1000);

};

};

Test chưa? Chưa test thì test đi rồi hãy đọc tiếp! Nhé!
.
.
.
Okay, nếu bạn đã test xong, bạn sẽ thấy:

NÓ KHÔNG HOẠT ĐỘNG

Và đấy chính là lý do mình đề cập đến mục này: Outer Data-binding.

Phân tích hàm count10 của bạn, bạn sẽ thấy thao tác cập nhật giá trị của timer nằm trong một callback!

setInterval(function () {

 if (10 > $scope.timer) {

 $scope.timer++;

 } else {

 $scope.timer = 1;

 }

 }, 1000);

Tức là nó sẽ thực hiện ở NGOÀI ANGULARJS.

Vô lý! Rõ ràng là tôi viết nó trong một hàm nằm trong Controller cơ mà!

Đúng, bạn “viết nó trong Controller” . Nhưng đúng hơn thì bạn “định nghĩa nó trong controller”. Còn nó sẽ được call ở ngoài. Vì setInterval là một function ở ngoài AngularJS. Chính vì lý do này, Model của bạn có thay đổi giá trị, thì AngularJS cũng không thể bắt được sự kiện thay đổi giá trị đó.  Với ví dụ trên, nếu bạn ấn nút “Start count to 10” liên tục, bạn sẽ thấy giá trị có thay đổi, nhưng đấy là do sự kiện “ng-click” của nút bấm phát ra! Và, dù sao thì nó cũng không phải là mục đích của bài toán.

Làm sao đây?

Rất may là AngularJS có cung cấp một phương thức: $apply(), nhớ là có dấu ‘$’ nhé! Nó giúp bạn phát ra sự kiện TRONG AngularJS khi mà giá trị của $scope bị thay đổi ở NGOÀI AngularJS. Tất cả những gì bạn cần làm là gọi phương thức này ở đoạn code làm thay đổi giá trị của $scope:

var myController = function ($scope) {

$scope.timer = 1;

$scope.count10 = function () {

setInterval(function () {

if (10 > $scope.timer) {

$scope.timer++;

} else {

$scope.timer = 1;

};

$scope.$apply(); //<-- Add this

}, 1000);

};

};

Bây giờ thì hết thắc mắc đoạn code ở phần sử dụng Firebase chưa?

Một trong những vấn đề khó chịu của Angular khi model bị thay đổi ngoài Angular, bạn phải dùng $scope.$apply() để cập nhật. Dưới đây là 1 cách làm tương tự trong Angular

$scope.count10Angular = function() {

 $timeout(function() {

 if (10 > $scope.timer) {

 $scope.timer++;

 } else {

 $scope.timer = 1;

 }

 $scope.count10Angular();

 }, 1000);

 };

Để dùng đc $timeout bạn phải inject nó vào controller nhé !

Tags: