Chào mừng các bạn đến với bài cuối cùng của Series AngularJS cho người mới. Trong bài này chúng ta sẽ tiếp tục tìm hiểu về Dependencies và Services trong Angular.
Chắc hẳn sau bài trước khi nhìn vào file app.js chúng ta thật sự thì nó như đống bùi nhùi, nào là controller, nào là directive lẫn lộn cả lên.
Một cách đơn giản đễ dễ dàng quản lý source code cũng như maintain sau này đó là chia code ra thành các thành phần nhỏ hơn. Ví dụ.
app.directive('productTitle', function(){ | |
... | |
}); | |
app.directive('productGalery', function(){ | |
... | |
}); | |
app.directive('productBuyButton', function(){ | |
... | |
}); | |
app.directive('productPanels', function(){ | |
... | |
}); |
Hoàn toàn có thể thấy phần này là những directive liên quan đến việc hiển thị sản phẩm vậy chúng ta có thể mang phần này ra một chỗ khác ngoài file app.js. Vậy chúng ta cùng tạo ra file products.js rồi chuyển hết phần code kia vào. Chúng ta tạo một module mới như sau.
(function() { | |
var app = angular.module('store-products', []); | |
app.directive('productTitle', function(){ | |
... | |
}); | |
app.directive('productGalery', function(){ | |
... | |
}); | |
app.directive('productBuyButton', function(){ | |
... | |
}); | |
app.directive('productPanels', function(){ | |
... | |
}); | |
})(); |
Đừng quên đặt code trong closure nhé, ở những closure khác nhau thì những biến cùng tên là hoàn toàn khác nhau, vì thế đừng băn khoăn về var app nhé, thì còn nếu bạn nào còn thắc mắc về closure thì hãy xem qua bài JavaScript Closures để hiểu về closure nhé. Sau khi đã chuyển code ra file, việc tiếp theo chúng ta cần làm là khai báo cho angular biết module store của chúng ta sẽ phải sử dụng thêm module store-products để chạy. Trong bài đầu tiên tôi đã có nói về cách khai báo dependencies cho một module. Các bạn có thể xem lại ở bài Directive, Module, Expression.
(function () { | |
var app = angular.module('store', ['store-products']); | |
... | |
})(); |
Và đừng quên import file products.js trong file index.html.
Chạy thử và mọi thứ vẫn y nguyên như lúc ban đầu, nhưng code của chúng ta đã gọn gàng ngăn nắp hơn nhiều.
Các bạn chắc chắn sẽ có câu hỏi về việc làm sao tôi có thể sắp xếp source code một cách gọn gàng?
Cách tốt nhất để chia nhỏ module là dựa trên chức năng. Chẳng hạn như, app.js sẽ là top-level module dùng để khai báo trong ng-app, products.js sẽ chứa những chức năng của product và chỉ product mà thôi.
Lại liếc qua source code, có cái gì đó lạ lạ ở đây, chúng ta đang fix cứng dữ liệu trong file app.js.
Vậy làm thế nào chúng ta có thể lấy dữ liệu? Cái chúng ta cần sẽ là gọi server để trả về JSON data, có thể là Ajax call chẳng hạn. Tuy nhiên Angular cung cấp cho chúng ta một bộ các service có sẵn để làm việc này. Như $http để gửi một HTTP request, và handle response, tương tự như Ajax, $log để log ra console tương tự console.log(), $filter dùng để lọc dữ liệu trong array, và một số các serivce khác.
Trong trường hợp của chúng ta, vì cần gửi request lên server để nhận dữ liệu nên chúng ta sẽ sử dụng service $http. Cách sử dụng như sau.
// usual | |
$http({ method: 'GET', url: '/products.json' }); | |
// using with shortcut method | |
$http.get('/products.json', { apiKey: 'myApiKey' }); |
$http sẽ trả về một Promise object, với hai callback method success() và error() để bạn có thể handle dữ liệu ở mỗi trường hợp. Về Promise object các bạn có thể xem sơ qua ở đây http://javascriptplayground.com/blog/2015/02/promises/, trong tương lai tôi sẽ viết một bài viết khác để giải thích rõ hơn về vấn đề này.
Vậy thì làm sao để controller nói cho Angular biết nó sử dụng một service nào đó, $http chẳng hạn?
Chúng ta sử dụng một cấu trúc gọi là array syntax. Dạng như sau.
app.controller('SomeController', [ '$http', '$log', function($http, $log) { | |
... | |
}]); |
Như các bạn thấy trong array được khai báo, phần tử cuối cùng sẽ là function với param truyền vào là các service name, các phần tử trước đó sẽ là service name. Kiểu làm thế này chính là Dependency Injection. Bên blog Tôi đi code dạo có một loạt bài giới thiệu và trình bày về khái niệm này khá rõ ràng, các bạn nên tìm đọc bài Dependency Injection và Inversion of Control. Về Dependency injection trong Angular tôi có thể khái niệm sơ lược như sau.
Khi Angular load app sẽ có một thứ gọi là Injector được tạo ra, khi các service được load nó sẽ thông báo cho Injector biết rằng, tao đã sẵn sàng để được sử dụng. Khi Controller được load nó sẽ thông báo cho Injector biết rằng khi run tao cần sử dụng những thứ như $http, $log… Khi nhận được thông báo này Injector sẽ trả lời Cool, tao biết rồi và wrap hết tất cả các serivce vừa được yêu cầu trả về cho controller, đây mày lấy hết đi.
Câu chuyện như vậy chính là Dependency injection, tại sao lại gọi vậy? bởi vì như chúng ta thấy service được inject trực tiếp vào controller để sử dụng như là một argument.
Việc cuối cùng là chỉnh sửa controller cho đúng để lấy dữ liệu.
app.controller('StoreController', ['$http', function ($http) { | |
var store = this; | |
store.products = []; | |
$http.get('/products.json').success(function(data){ | |
console.log(data) | |
}); | |
}]); |
Ngoài ra các bạn có thể xem thêm các cách dùng khác của $http tại https://docs.angularjs.org/api/ng/service/$http
Source code trong bài các bạn có thể tìm thấy ở https://github.com/codeaholicguy/angular-tutorial
Tóm lại hy vọng qua series này các bạn đã có đủ kiến thức để tự tạo cho mình một application bằng Angular 1.x. Hẹn gặp lại các bạn trong nhưng bài viết tiếp theo.
mình bị lỗi như này khi gọi đến localhost:8180/users
angular.js:10722XMLHttpRequest cannot load localhost:8180/users. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.(anonymous function) @ angular.js:10722
bạn tham khảo thử http://stackoverflow.com/questions/27742070/angularjs-error-cross-origin-requests-are-only-supported-for-protocol-schemes
mình đã chạy thử bằng xampp và ionic rồi nhg nó lại bị lỗi XMLHttpRequest cannot load http://localhost:8180/users. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8100’ is therefore not allowed access.
à xin lỗi, mình không bị thế nữa
Một thắc mắc nữa là trong tutorial của bạn, bạn không dùng $scope mà dùng $this khi handle các biến trong controller, còn các tutorial khác mình tham khảo được, chẳng hạn cái này: http://jsfiddle.net/toddmotto/xW4HM/ thì hay dùng $scope. Vậy cách dùng this có hoàn toàn thay thế được $scope trong controller?
$scope là phương tiện giao tiếp của controller và view, nó là một object, còn this nó trỏ tới current context, về cách nhìn thì nó khá là giống nhau, tuy nhiên mình nghĩ bạn nên tìm hiểu về từ khoá this trong javascript để có thể hiểu được tại sao $scope lại là phương thức sử dụng được prefer hơn.
nếu dùng this, đôi lúc sẽ không kiểm soát được this đang chỉ đến context nào trong code hả bạn?
chính xác
Mình còn một thắc mắc nữa, mình đã sửa url thành
$http.get(‘products.json’).success(function(data){
this.products = data;
});
thì gặp lỗi angular.js:12477 SyntaxError: Unexpected token n in JSON at position 10 at Object.parse (native) (để /products.json thì không đúng)
Thanks bạn,
trước tiên mình nghĩ nên revalidate xem cấu trúc json đã hợp lệ hay chưa đã, và response trả về có phải là dạng json hay ko
Sorry, mình quên check lại file products.json :p
bạn ơi, mình chưa hiểu đoạn code gọi $http để get data về:
store.products = [];
$http.get(‘/products.json’).success(function(data){
console.log(data)
});
mình thấy trong callback không có đoạn nào gán dữ liệu lấy về cho store.products mà chỉ có console.log(data), nhưng đoạn code này vẫn get được data tương đương đoạn code cũ: this.products = products;
Thanks bạn,
chào bạn,
đầu tiên cảm ơn bạn vì câu hỏi khá thú vị, đoạn code này sẽ không thể chạy được trừ khi bạn gán store.products = data, đây là trick mà mình đặt trong code để các bạn có thể sửa và hiểu làm thế nào callback hoạt động.
mong bạn sẽ tiếp tục ủng hộ các bài viết của mình
mình cũng thấy nó có gì đó không đúng, cám ơn bạn.