9 Lỗi thường gặp khi lập trình Ionic
Giới thiệu Ionic, được ra mắt hai năm trước, là một bộ công cụ tuyệt vời dùng cho phát triển hybrid application (ứng dụng lai) dựa trên AngularJS. Ionic hiện đang rất phổ biến với kho ứng dụng lên đến con số hàng triệu cùng một cộng đồng mạnh mẽ với hàng nghìn developer. Ionic ...
Giới thiệu
Ionic, được ra mắt hai năm trước, là một bộ công cụ tuyệt vời dùng cho phát triển hybrid application (ứng dụng lai) dựa trên AngularJS. Ionic hiện đang rất phổ biến với kho ứng dụng lên đến con số hàng triệu cùng một cộng đồng mạnh mẽ với hàng nghìn developer.
Ionic không phải là mới. Trong khi đó, công nghệ web cùng các công cụ tối ưu lại đang thay đổi chóng mặt hằng ngày. Vì vậy, không dễ quyết định hướng đi cho một dự án mới. Trong điều kiện như vậy, developer có thể phạm lỗi và ảnh hưởng đến chất lượng của ứng dụng hoặc hiệu suất của team.
Hy vọng với những lỗi thường gặp dưới đây, các bạn sẽ có thể tránh được các vấn đề cơ bản, đồng thời gia tăng hiệu suất và quy mô ứng dụng khi sử dụng Ionic.
Ionic đang rất được ưa chuộng với hơn một triệu ứng dụngLỗi thường thấy #1: Quên kích hoạt Native Scrolling
Native Scrolling Cho phép Ionic nhận dạng sự kiện cuộn trên các webview được hỗ trợ. Ta có thể tạo Pull to Refresh (Kéo để làm mới), List Reordering (Sắp xếp lại danh sách) và Infinite Scroll (cuộn vĩnh viễn) mà không cần JavaScript scrolling (ta chỉ cần JavaScript scrolling khi trình duyệt thiếu scroll event chuẩn).
Trên android, kể thừ phiên bản Ionic 1.2 (tháng 12, 2015), native scrolling được mặc định kích hoạt. Native scrolling đã cải thiện đáng kể hiệu suất và UX, giúp scroll mượt hơn nhờ asynchronous event.
Tiếc thay, do thiếu event chuẩn, nên native scrolling vẫn chưa được hỗ trợ trên nền tảng iOS.
Nếu bạn đang dùng phiên bản trước 1.2, bạn có thể bật Native Scrolling trên Android bằng cách dùng $ionicConfigProvider
1 2 3 4 |
// Enable Native Scrolling on Android $ionicConfigProvider.platform.android.scrolling.jsScrolling(false); |
Bạn cũng có thể bật và tắt Native scrolling trên mọi trang bằng cách sử dụng overflow-scroll điều hướng mọi ion-content:
1 2 3 4 |
<!-- Disable Native Scrolling on this page only --> <ion-content overflow-scroll=”false”> |
Nên nhớ, không may là collection-repeat (cho phép ứng dụng hiện danh sách với nhiều đề mục) vẫn chưa được native scrolling hỗ trợ.
Lỗi thường thấy #2: Không dùng Ionic CLI để cài đặt Platform và Plugin
Ionic CLI thêm nhiều tính tính năng hay vào Cordova CLI. Plafrorm và plugin persistence là một trong các tính năng đó.
Tuy nhiên, với Cordova CLI, platform và plugin của bạn chỉ được cài đặt trên hệ thống riêng thôi. Và khi làm việc nhóm, bạn sẽ muốn chia sẻ môi trường, platform và plugin để tránh bug. Cũng với Cordova CLI, ta khó có thể đồng bộ dự án giữa nhều hệ thống. Tất nhiên, bạn cũng có thể commit các thư mục platform và plugin, nhưng chúng tôi không khuyên dùng cách này.
Khi dùng Ionic CLI để cài đặt platform ionic platform add ios và plugin ionic plugin add camera, file package.json sẽ được edit đúng cách.
Platform và plugin được lưu trữ trong thuộc tính cordovaPlatforms và cordovaPlugins:
1 2 3 4 5 6 7 8 9 10 11 |
"cordovaPlugins": [ "cordova-plugin-whitelist@1.0.0", "cordova-plugin-inappbrowser@1.0.1", "cordova-plugin-splashscreen@2.1.0" ], "cordovaPlatforms": [ "android", "ios" ] |
Giờ đây, các developer khác đã đồng bộ dễ dàng hơn khi tạo code mới, đơn giản chỉ việc chạy ionic state restore khi cần thiết (thêm, xóa hoặc up phiên bản)
Lỗi thường thấy #3: Thinking Performance Comes out of the Box
Ionic được dựa trên AngularJS, và hiệu suất trên thiết bị vẫn chưa rõ. Về điểm này, tôi cam đoan với các bạn: chỉ với một ít kiến thức AngularJS thôi, các bạn có thể xây dựng một ứng dụng tầm cỡ với Ionic.
Một ví dụ điển hình là Sworkit app (được xây dựng với Ionic) với hơn 9 triệu người dùng, 7 triệu lượt tải và 4,5 sao trên Google Play.
Nếu muốn tận dụng tối đa AngularJS, bạn cần biết một số điều dưới đây trước khi bắt đầu dự án.
$watch
Với scope change trong AngularJS, nhìn chung có 4 kiểu $watch: $watch (normal), $watch (deep), $watchCollection và $watchGroup.
Mỗi kiểu đều khác nhau, và chọn kiểu phù hợp có thể thay đổi hiệu suất đáng kể.
$watch (normal)
Sử dụng $watch normal sẽ chỉ kiểm tra thuộc tích Object hiện có, hoặc các Array item. Những thay đổi nông (như thêm thuộc tính Object hay nhét một item với vào một Array) sẽ không được hỗ trợ.
1 2 3 4 5 6 7 |
$scope.$watch('watchExpression', function(newVal, oldVal){ if(newVal){ // watchExpression has changed. } }); |
$watch (deep)
$watch deep sẽ thực hiện các thay đổi nông và sâu, như chức năng Nested Object. Với $watch deep, bạn chắc chắn sẽ không bỏ lỡ bất cứ thay đổi nào. Tuy vậy, hãy sử dụng cẩn trọng vì $watch deep có thể thay đổi hiệu suất.
1 2 3 4 5 6 7 |
$scope.$watch('watchExpression', function(newVal, oldVal){ if(newVal){ // watchExpression has changed. } }, true); |
$watchCollection
Có thể cân nhắc dùng $watchCollection giữa $watch normal và $watch deep. $watchCollection cũng có thể so sánh object reference, và còn có thể shallow watches các thuộc tính của object bằng cách thêm thuộc tính Object hoặc đẩy item mới vào Array.
1 2 3 4 5 6 7 |
$scope.$watchCollection('watchExpression', function(newVal, oldVal){ if(newVal){ // watchExpression has changed. } }); |
$watchGroup
Được giới thiệu ở AngularJS 1.3, $watchGroup cho phép theo dõi nhiều expression cùng một lúc.
Tuy không thể cải thiện hiệu suất ứng dụng như với $watch normal, nhưng $watchGroup có thể theo dõi tổng hợp nhiều scopre expression.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$scope.$watchGroup([ 'watchExpression', 'watchExpression2', 'watchExpression3' ], function(newVals, oldVals) { if (newVals[0]) { // watchExpression has changed. } if (newVals[1]) { // watchExpression2 has changed. } if (newVals[2]) { // watchExpression3 has changed. } }); |
Track By
track by được dùng để tránh các thao tác DOM dư thừa khi dùng ng-repeat. Nếu digest cycle tìm được ít nhất một phần tử thay đổi trong collection, ng-repeat sẽ render lại mọi phần tử. Vì thao tác DOM luôn ảnh hưởng đến hiệu suất ứng dụng, nên ta càng hạn chế càng tốt.
track by có thể xem là một bộ lọc đặc biệt, cho phép ta cập nhật các phần tử cần thiết chứ không cần phải render lại cả collection.
1 2 3 4 5 6 7 |
<!-- if items have a unique id --> <div ng-repeat="item in items track by item.id"></div> <!-- if not, you can use the $index that ng-repeat adds to every of its items --> <div ng-repeat="user in users track by $index"></div> |
Chỉ cần nhớ, tránh dùng track by trên collection-repeat.
One-Time Binding
One-time binding (hoặc ::) được giới thiệu từ Angular 1.3, đã cải thiện đáng kể hiệu suất ứng dụng.
Về cơ bản, sử dụng one-time binding :: lên một expression sẽ loại expression đó khỏi danh sách $watchers khi quá tải. Đồng nghĩa với việc expression không thể cập nhật ngay cả khi dữ liệu thay đổi.
1 2 3 |
<p>{{::user.firstName}}</p> |
Lời khuyên của chúng tôi là hãy kiểm tra ứng dụng xem thử phần nào cập nhật được, phần nào không cập nhật được, và dùng one-time binding :: đúng theo đó. Khâu này sẽ giảm thiểu rất nhiều gánh nặng lên digest cycle.
Nên nhớ, one-time binding không thể được dùng trong collection-repeat, bởi vì list item trên màn hình sẽ thay đổi khi cuộn.
Và nếu bạn muốn tìm hiểu thêm về AngularJS và Ionic, tôi nghĩ các bạn nên đọc bài này Ultimate AngularJS and Ionic performance cheat sheet .
Lỗi thường thấy #4: Nhận thức logic View Cache còn mơ hồ
Ứng dụng trang sẽ không mặc định cache trang. Vì vậy khi sử dụng AngularJS, chắc hẳn bạn đã gặp cảnh trạng thái cuộn và trỏ nhập liệu không được lưu lại khi bạn duyệt tới hay lùi giữa các trang.
Với Ionic, mười trang sẽ được cache lại theo mặc định, và bạn có thể thay đổi tính năng này trên toàn hệ thống hoặc trên mỗi platform.
1 2 3 4 5 6 7 8 |
// Globally $ionicConfigProvider.views.maxCache(5); // Per platforms $ionicConfigProvider.platform.android.views.maxCache(5); $ionicConfigProvider.platform.ios.views.maxCache(5); |
Dù cache trang có thể hơi khó hiểu với người mới một chút nhưng đây vẫn là một tính năng rất hay.
Vấn đề là nếu người dùng quay lại trang đã cache, controller sẽ không được tái khởi tạo như ở các ứng dụng AngularJS.
Với điều kiện như thế này, các bạn sẽ cập nhật dữ liệu trên trang như thế nào?
Giới thiệu các event vòng đời của controller
So với AngularJS, Ionic đưa ra nhiều event vòng đời hơn:
1 2 3 4 5 6 7 8 9 10 |
$scope.$on('$ionicView.loaded', function(){}); $scope.$on('$ionicView.unloaded', function(){}); $scope.$on('$ionicView.enter', function(){}); $scope.$on('$ionicView.leave', function(){}); $scope.$on('$ionicView.beforeEnter', function(){}); $scope.$on('$ionicView.beforeLeave', function(){}); $scope.$on('$ionicView.afterEnter', function(){}); $scope.$on('$ionicView.afterLeave', function(){}); |
Những sự kiện này rất cần thiết nếu bạn muốn kiểm soát view cache.
Ví dụ như event $ionicView.loaded sẽ được kích hoạt ngay khi view được load. Nếu view này được cache, event này sẽ không được kích hoạt nữa, ngay cả khi người dùng quay lại. Đây thường là event bạn sẽ dùng để khởi động biến như event $viewContentLoaded trong AngularJS.
Nếu bạn muốn fetch dữ liệu mỗi khi truy cập view (được cache hoặc không), bạn có thể dùng event $ionicView.enter.
Khi dùng đúng event vào đúng lúc, bạn có thể cải thiện độ tiện dụng của ứng dụng.
Về phần hiệu suất, dùng cache view chỉ ảnh hưởng lên kích thước của DOM thôi. Khi trang được cache, mọi người xem sẽ bị ngắt kết nối và trang sẽ chuyển vào DOM để đợi được sử dụng lại.
Tuy kích thước của DOM sẽ ảnh hưởng đáng kể đến hiệu suất, nhưng cache mười trang vẫn chấp nhận được (dĩ nhiên còn phụ thuộc vào nội dung load của trang).
Lỗi thường gặp #5: Không biết về Crosswalk cho Android
Mỗi phiên bản Android chay một WebView (trình duyệt chạy ứng dụng) khác nhau. Hiệu suất sẽ khác với mỗi thiết bị, và đặc biệt tệ trên thiết bị Android đời cũ. Bạn có thể cài đặt Crosswalk để có trải nghiệm mượt mà như nhau trên mọi thiết bị Android. Crosswalk sẽ thêm trình duyệt Chromium mới nhất vào ứng dụng của bạn (20Mb mỗi APK, cả với ARM và X86).
Crosswalk có thể được cài đặt chỉ bằng cách dùng Ionic CLI hoặc Cordova CLI:
1 2 3 |
ionic plugin add cordova-plugin-crosswalk-webview |
Lỗi thường gặp #6: Cố gắng chạy Cordova Plugin trong trình duyệt
Đa số developer dùng Ionic sẽ muốn ứng dụng của mình chạy trên iOS và Andoid. Sau khi thêm platform ionic platform add ios android và vài plugin ionic plugin add cordova-plugin-device-orientation cordova-plugin-contacts, nhiều người nghĩ họ có thể test trên trình duyệt được, đây là một lỗi sơ đẳng bởi không phải trình duyệt nào cũng có thể áp dụng cách này.
Plugin của Cordova được thiết kế để giao tiếp với thiết bị API native thông qua JavaScript. Plugin giao tiếp hoặc plugin định hướng thiết bị vì thế sẽ chỉ làm việc trên một thiệt bị.
Tuy nhiên, bạn có thể dễ dàng test code trên thiết bị và gỡ lỗi từ xa bằng máy tính.
Gỡ lỗi từ xa trên Android
Kết nối thiết bị, hãy chắc chắn rằng thiết bị được máy tính nhận biết bằng lệnh adb devices (bạn cần có Android SDK).
Build app và cài đặt vào thiết bị bằng lệnh ionic run android. Khi thiết bị khởi động ứng dụng, dùng Chrome dev tools mở console (trên máy tính) chrome://inspect/#devices, và kiểm tra thiết bị.
Gỡ lỗi từ xa trên iOS
Kết nối thiết bị, hãy chắc chắn rằng thiết bị được máy tính nhận biết. Build app và cài đặt vào thiết bị bằng lệnh ionic run ios –device.
Khi ứng dụng đã được khởi động, click vào Develop > Your iPhone > Your app để mở Safari dev tools (trên máy tính) :
Chạy Cordova Plugin trong trình duyệt
Chạy Cordova plugin trong trình duyệt là một chức năng cao cấp mà bạn nên biết. Từ Ionic 1.2, trình duyệt dã được hỗ trợ chính thức, mở ra kỷ nguyên ứng dụng cross-platform vượt ra khỏi iOS và Android.
Với Cordova Browser platform, Electron và công nghệ Web (JavaScript, HTML, và CSS) thôi, ta giờ đây đã có thể build ứng dụng Ionic cho trinhd duyệt và desktop (Windows, Linux và OSX)
Bạn có thể tìm starter kit trên Github.
1 2 3 |
cordova platform add browser |
Ứng dụng của bạn cần phải được biên soạn lại trước khi sử dụng được đúng như trên iOS hoặc Andoid:
1 2 3 |
cordova run browser |
Lệnh này sẽ biện soạn ứng dụng và mở trình duyệt mặc định.
Cross-platform plugin
Nhiều plugin như: Network, Camera và Facebook có hỗ trợ cùng lúc iOS, Android, và platform Browser cùng một lúc – tất cả với cùng API.
Bạn có thể biết được liệu thiết bị đang online hay offline trên mọi platform (iOS, Android, Trình duyệt và Desktop) bằng cách sử dụng ngCordova API:
1 2 3 4 5 6 7 8 9 10 11 |
// listen for Online event $rootScope.$on('$cordovaNetwork:online', (event, connectionType) => { this.isOnline = true; }); // listen for Offline event $rootScope.$on('$cordovaNetwork:offline', (event, connectionType) => { this.isOnline = false; }); |
Giờ đây, chắc hẳn bạn đã nghĩ đến việc tạo ra các sản phẩm chạy được ở bất cứ đâu chỉ với một dòng code base.
Lỗi thường thấy #7: Đi theo cấu trúc Starter Kit với các ứng dụng quy mô lớn
Khi dùng lệnh ionic start myapp, một dự án mới tạo sẽ đi theo cấu trúc folder sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
www/ js/ app.js controllers/ aaa.js bbb.js ccc.js services/ xxx.js yyy.js zzz.js templates/ aaa.html bbb.html ccc.html |
Đây được gọi là cấu trúc Folder-by-Type. Với cấu trúc này, các file JavaScript, CSS, và HTML sẽ được nhóm theo kiểu. Cấu trúc này trông có vẻ dễ cho người mới, nhưng bạn sẽ sớm mất kiểm soát khi quy mô quá lớn.
Một vài lý do không nên dùng cấu trúc Folder-by-Type:
- Số file trong folder có thể trở nên quá lớn
- Với một tính năng cụ thể. sẽ khó tìm tất cả các file mà bạn cần điều chỉnh
- Làm việc với một tính năng sẽ dẫn đến quá nhiều folder mở
- Ứng dụng càng lớn, càng khó làm việc
Với ứng dụng quy mô lớn, tôi khuyên dùng cấu trúc Folders-by-Feature, các file JavaScript, CSS, và HTML lúc này sẽ được nhóm theo tính năng hoặc module AngularJS: