Tìm hiểu về 'Resolve' trong AngularJS routes provider

Trong bài Tìm hiểu module ngRoute trong AngularJS, chúng ta đã tìm hiểu cách làm thế nào để định tuyến (routing) và cách các routes nói chuyện với controller như thế nào. Việc khởi tạo các controller có thể khác nhau dựa trên các thiết lập của route và các sự kiện $routeChangeSuccess và $routeChangeError tương ứng. Hầu hết các sự kiện này có do thuộc tính resolve khi cấu hình route. Trong bài này, chúng ta sẽ làm sáng rõ vấn đề Tại sao lại cần resolve và cách thức hoạt động của nó như thế nào.
Tôi tìm được một bài viết của Shidhin giải thích khá rõ ràng và dễ hiểu về thuộc tính ‘Resolve’ trong AngularJS routesthông qua ba phiên bản của một câu chuyện. Do đó, tôi sẽ dịch lại bài viết đó và giữ nguyên câu chuyện của tác giả để lưu lại kiến thức cho bản thân và chia sẻ bài viết bổ ích này tới các bạn quan tâm.


Nội dung

Câu chuyện
Phiên bản 2 của câu chuyện
Phiên bản 3 của câu chuyện

Câu chuyện

Vào một ngày đẹp trời, tôi đã lên kế hoạch du lịch Thụy Sĩ. Vì vậy tôi gọi điện cho điều hành tour. Anh ta giải thích cho tôi về hành trình và thông tin khách sạn rất chi tiết và rõ ràng, vì vậy tôi đã đề nghị anh ta chuẩn bị các thứ cần thiết. Sau đó, điều hành tour có trách nhiệm đặt phòng và thực hiện các yêu cầu cần thiết cho chuyến du lịch của tôi. Sau cùng, tôi đã có một chuyến du lịch đến Thụy Sĩ.
Bây giờ, chúng ta chuyển câu chuyện này sang ngữ cảnh của AngularJS. Chúng ta có:
  • visitplace là route đầu tiên của chúng ta.
  • Địa điểm, các khu vực lân cận, phòng khách sạn là các nội dung trong template. Chúng ta sẽ thêm chúng vào trong file placetovisit.html.
  • Điều hành tour là người kết nối tới địa điểm, do đó chúng ta có một controller tên là TourCoordinatorCtrl.
Nội dung của file app.js như sau:
var app = angular.module("app", ["ngRoute"]);

app.config(function($routeProvider) {
  $routeProvider.when("/visitplace", {
    templateUrl: "placetovisit.html",
    controller: "TourCoordinatorCtrl"
  });
});

app.factory("accommodation", function() {
  return {
    hotelName: function() {
      return "Some Hotel";
    },
    roomNo: function() {
      return "203";
    }
  };
});

app.controller("TourCoordinatorCtrl", function($scope, accommodation) {
  $scope.name = "Shidhin";
  $scope.place = "Switzerland";
  $scope.hotel = accommodation.hotelName();
  $scope.roomno = accommodation.roomNo();
});

Template placetovisit.html:
<h1>Trip details</h1>
<div>
  <h2>Hi, {{name}}. Welcome to {{place}}</h2>
  <div>The accomodation is arranged on <strong>{{hotel}}</strong> and room number is <strong>{{roomno}}</strong></div>    
</div>
Và file index.html:
<!DOCTYPE html>
<html lang="en" ng-app="app">

  <head>
    <meta charset="UTF-8" />
    <title>AngularJS Resolve routes example</title>
    <script src="https://code.angularjs.org/1.2.25/angular.js"></script>
    <script src="https://code.angularjs.org/1.2.25/angular-route.js"></script>
    <script src="app.js"></script>
  </head>

  <body>
    <a href="#/visitplace">I want to go for a trip</a>
    <div ng-view></div>
  </body>

</html>
Mọi thứ diễn ra như mong đợi khi chúng ta đìều hướng đến route /visitplace. Ở đây, chúng ta thấy rằng TourCoordinatorCtrl phụ thuộc vào accommodation service. Controller chỉ chịu trách nhiệm tìm chi tiết khách sạn và phòng. Ngoài ra, việc lấy chi tiết về khách sạn và phòng là các sự kiện hoàn toàn đồng bộ. Điều đó có nghĩa là căn phòng sẽ được bố trí trước khi tôi tới nơi.
Nhưng điều gì sẽ xảy ra nếu các sự kiện là không đồng bộ? Hãy nghĩ theo hướng này: Điều gì xảy ra nếu điều hành tour không chuyên nghiệp và anh ta đã quên đặt phòng và khách sạn? Do đó, khi tôi tới nơi tôi sẽ phải chờ cho tới khi tôi lấy được một phòng mới. Tương tự như vậy, trong code, chúng ta có thể thấy rằng view sẽ được trả lại (render) nhưng phương thức getHotel() và getRoom() sẽ lấy thêm thời gian để có được thông tin chi tiết phòng.
var app = angular.module("app", ["ngRoute"]);
app.config(function($routeProvider) {
  $routeProvider.when("/visitplace", {
    templateUrl: "placetovisit.html",
    controller: "TourCoordinatorCtrl"
  });
});
app.factory("accommodation", function($timeout) {
  return {
    hotelName: function(scope) {
      $timeout(function() {
        scope.hotel = "Some hotel";
      }, 3000);
      return "---";
    },
    roomNo: function(scope) {
      $timeout(function() {
        scope.roomno = "103";
      }, 3000);
      return "---";
    }
  };
});
app.controller("TourCoordinatorCtrl", function($scope, accommodation) {
  $scope.name = "Shidhin";
  $scope.place = "Switzerland";
  $scope.hotel = accommodation.hotelName($scope);
  $scope.roomno = accommodation.roomNo($scope);
});
Đây không phải là một ý tưởng tốt. Chúng ta phải trả lại view và đợi dữ liệu được nạp lên đó. Trong trường hợp của tôi, nó giống như việc tôi đã bắt đầu chuyến đi và đã đến nơi, nhưng tôi đang phải chờ đợi ở đó để có được nơi ăn nghỉ của mình. Điều này thực sự gây khó chịu và phiền nhiễu. Liệu có cách nào có thể đảm bảo rằng các phụ thuộc kia được giải quyết trước khi tôi bắt đầu chuyến đi của mình? Hãy xem:

Phiên bản 2 của câu chuyện

Tôi có một người bạn, cậu ta là chủ một khách sạn tại Thụy Sĩ. Cậu ta là bạn rất thân của tôi, vì vậy tôi có thể đề nghị cậu ta để cho tôi một phòng bất cứ lúc nào. Nếu tôi làm như vậy thì tôi không còn phải phụ thuộc vào nơi ở do điều hành tour cung cấp nữa. Cuối cùng, tất cả những gì tôi phải làm là đảm bảo chắc chắn rằng bản thân tôi giải quyết được vấn đề chỗ ở và thông báo với điều hành tour thu xếp các vấn đề còn lại.
Đây chính xác là những gì chúng ta cần nếu bản thân các routes cần giải quyết các phụ thuộc trong ứng dụng. Với vấn đề này, Angular cung cấp một thuộc tính resolve trong cấu hình của dịch vụ $routeProvider. Thuộc tính resolve là một đối tượng sơ đồ tùy chọn (an optional map object) hoặc một mảng tên các dịch vụ hiện có (array of existing service names). Tất cả các keys của map object có thể được bơm truyền vào controller như một phụ thuộc. Key là một chuỗi (string) đơn giản, và giá trị của nó có thể là một function hoặc một string. Nếu giá trị là string thì Angular sẽ xem đó là một dịch vụ hiện có (existing service) và bơm truyền vào controller. Nếu giá trị là một function thì nó sẽ hoạt động như một factory function và giá trị trả về sẽ được bơm truyền vào controller.
Hãy liên hệ câu chuyện hiện tại với ngữ cảnh của Angular bằng cách sử dụng đối tượng resolve. Hãy xem những thay đổi trong code của ứng dụng, file app.js:
var app = angular.module("app", ["ngRoute"]);
app.config(function($routeProvider) {
  $routeProvider.when("/visitplace", {
    templateUrl: "placetovisit.html",
    controller: "TourCoordinatorCtrl",
    resolve: {
      "myFriendsHotel": function() {
        return {
          hotelName: function() {
            return "My Friend's hotel";
          },
          roomNo: function() {
            return "100";
          }
        };
      }
    }
  });
});

app.controller("TourCoordinatorCtrl", function($scope, myFriendsHotel) {
  $scope.name = "Shidhin";
  $scope.place = "Switzerland";
  $scope.hotel = myFriendsHotel.hotelName();
  $scope.roomno = myFriendsHotel.roomNo();
});
Lưu ý: Khi controller đã được định nghĩa trong route, không bao giờ khởi tạo lại nó thông qua ng-controller directive, điều đó có thể đưa ra lỗi khi sử dụng với thuộc tính resolve.
Bằng cách này, chúng ta có thể chắc chắn rằng việc khởi tạo controller sẽ bị trì hoãn cho đến khi khách sạn đã sẵn sàng. View sẽ chỉ được trả lại khi controller đã được khởi tạo và sự kiện $routeChangeSuccess sẽ được kích hoạt. Cũng cần chú ý rằng, việc tìm khách sạn ở đây là một hành động đồng bộ; có nghĩa là khi bạn tôi là chủ khách sạn, tôi có phòng ngay lập tức. Trong các ứng dụng thực tế, hầu hết chúng ta phải đối phó với các hành động không đồng bộ. Nào, hãy chuyển đến phiên bản thứ 3 của câu chuyện.

Phiên bản 3 của câu chuyện

Trong phiên bản trước, bạn của tôi là chủ khách sạn. Nhưng trong phiên bản này, sẽ không có chủ khách sạn nào cả. Lần này, tôi có một người bạn khác ở Thụy Sĩ, cô ấy biết một vài chủ khách sạn và có thể tìm phòng cho tôi với giá rẻ hơn. Khi tôi gọi điện cho cô ấy và nói yêu cầu của mình, cô ấy sẽ mất một thời gian để nói chuyện với bạn của cô ấy và thu xếp cho tôi một phòng. Và thời gian đó không thể đoán trước được bởi vì bạn tôi sẽ yêu cầu bạn bè của cô ấy, bạn cô ấy có thể nói chuyện với bạn của họ và cứ như thế. Nhưng khi tôi gọi cho cô ấy lần đầu tiên, cô ấy hứa với tôi rằng cô ấy chắc chắn sẽ cập nhật kết quả, do đó tôi có thể thu xếp phần còn lại. Vì vậy, chỉ khi tôi nhận được thông tin cập nhật từ cô ấy, tôi mới gọi cho điều hành tour và đề nghị anh ta thu xếp các phần còn lại.
Hãy xem những thay đổi trong file app.js ở phiên bản cuối này:
var app = angular.module("app", ["ngRoute"]);

app.config(function($routeProvider) {
  $routeProvider.when("/visitplace", {
    templateUrl: "placetovisit.html",
    controller: "TourCoordinatorCtrl",
    resolve: {
      "accommodation": function($q, $timeout) {
        var myFriend = $q.defer();
        $timeout(function() {
          myFriend.resolve({
            hotelName: function() {
              return "My Friend's friend's hotel";
            },
            roomNo: function() {
              return "404";
            }
          });
        }, 5000);
        return myFriend.promise;
      }
    }
  });
});

app.controller("TourCoordinatorCtrl", function($scope, accommodation) {
  $scope.name = "Shidhin";
  $scope.place = "Switzerland";
  $scope.hotel = accommodation.hotelName();
  $scope.roomno = accommodation.roomNo();
});
Trong đoạn code trên, các bạn thấy rằng chúng ta đã định nghĩa một biến myFriend bên trong hàm resolve và khởi tạo nó bằng cách sử dụng hàm $q.defer() là bộ thực thi Promises API trong AngularJS, được lấy cảm hứng từ thư viện “Q” được kriskowal thực hiện. Nếu bạn không biết Promises là gì, bạn nên tìm hiểu về nó tại đây.
Những gì xảy ra ở đây là: function resolve đã tạo ra biến myFriend và ngay lập tức nó trả lại myFriend.promise. Điều này có nghĩa, chúng ta biết rằng điều gì sẽ xảy ra sau đó và myFriend sẽ cập nhật kết quả của hành động để trả lại lời hứa. Hành động có thể thành công hoặc thất bại. Nếu hành động thành công, lời hứa sẽ được giải quyết (resolved) với các thông tin về phòng khách sạn; nếu hành động thất bại, lời hứa sẽ bị từ chối (reject) với dữ liệu trả lại (thường là các thông báo lỗi).
Khi factory function trả lại một lời hứa (promise), việc khởi tạo controller sẽ bị trì hoãn cho đến khi lời hứa được giải quyết hoặc từ chối (resolved/rejected). Khi promise được giải quyết, controller sẽ được khởi tạo và dữ liệu được trả lại sẽ được bơm vào trong controller. Trong trường hợp của chúng ta thì đó là đối tượng accommodation; sau đó sự kiện $routeChangeSuccess sẽ được kích hoạt. Cũng có khả năng là promise đầu tiên trả lại một promise khác. Trong trường hợp đó, việc khởi tạo controller của chúng ta sẽ bị trì hoãn cho đến khi tất cả các promises được giải quyết. Tại bất kỳ thời điểm nào, nếu một promise bị từ chối, controller sẽ không được khởi tạo, thay vào đó là sự kiện $routeChangeError sẽ được kích hoạt.
Trong kết quả demo dưới đây, chúng ta sẽ thấy view được trả lại sau 5 giây kể từ khi click vào link “I want to go for a trip”:

Nguồn : http://thapsang.net


Nhận xét