Ionic AngularJS et Cordova

, par MiKaël Navarro

Un exemple d’application faite avec le framework Ionic basé sur AngularJS et Apache Cordova.

Le projet que je vous propose de suivre est en fait une évolution d’une application HTML5, initié lors de ma formation d’une semaine avec Renaud Dubuis, à savoir un lecteur de flux RSS.

L’idée est d’avoir une application mobile pour facilement pouvoir :
 Lire les dernières « nious » des sites syndiqués
 Rechercher / filtrer les articles
 Enregistrer / partager des favories

Présentation

 NodeJS :
NodeJS est un interpréteur local (machine).
C’est devenu le socle des outils des technologies « frontend ».

  1. node (interpréteur)
  2. npm (gestionnaire de modules)

 PhoneGAP / Apache Cordova :
Permet d’appeler les APIs natives des SDK mobiles depuis le JavaScript.
PhoneGAP encapsule des composants HTML5 et JavaScript dans une WebView.

 AngularJS :
Elevation du HTML en tant que template applicatif au moyen de directive et d’expression.
Peut être appréhendé comme un moteur de gestion des relations entre une logique métier dévelloppée en JavaScript et des vues de restitution HTML (« Data Binding »).

 Ionic :
Ensemble d’outils pour le développement d’applications mobiles hybrides.
WorkFlow de développemnt (Gulp, Browser Sync, Bower).
S’appuie sur AngularJS pour la partie application web du framework et sur Apache Cordova pour la partie construction des applications natives.
Ionic CSS simplifie la création de l’UI.

Autres frameworks :
 jQuery Mobile est un framework UI tactile bâtie sur jQuery (à éviter car peu performant aujourd’hui sur mobile).
 Mobile Angular UI est un autre framework HTML5 qui utilise bootstrap3 et à nouveau AngularJS pour créer des applications mobiles interactives.

Outils de développement

 On commencera par le maintenant très connu SublimeText dans sa version 3 :
C’est un éditeur de texte générique disponible sur Windows, Mac et Linux.
Sa particularitée est qu’il très rapide et propose une foule d’extensions pour nous faciliter la vie (comme Emmet, des snippets pour AngularJS et Ionic, …).

Rajouter le Package Control, puis pour installer un package :

CTRL+MAJ+P (palette)
> ip (install package)
ENTER
> NomDuPackage
ENTER

Packages utiles :
 SideBarEnhancements
 Terminal
 Emmet
 JavaScript Snippets
 JavaScript Completions
 BootStrap 3 Snippets

Mais on pourra aussi citer l’incontournable Emacs :
Avec aussi ses packages Emmet, AutoComplete, Yasnippet, Angular Snippet, …

 Les Chrome Dev Tools :
Intégrés au navigateur Chrome, ils apporte des fonctionnalités supplémentaire comme :

  • Timeline (capture du flux de rendu)
  • Simulateur Mobile (visualiation des breakpoint)
  • Simulation du débit
  • Insepction de Devices

A noter que Firefox propose aussi sa distribution Firefox Developper Edition destinée aux développeurs ainsi que des plugins comme FireBug, FireQuery, …

 Lite Server :
C’est une encapsulation de Browser Sync :

  • server http
  • synchronisation multi-device
  • outils de remote debugging
  • simulation du débit

Installation :
$ npm i ‐g lite‐server

Utilisation (depuis le répertoire racine voulu) :
$ lite‐server
Puis à chaque modification des fichiers source les clients connectés seront automatiquement mis à jour…

Installation

Commençons par installer Ionic :
 Installez node.js ;
 Et les outils en ligne de commande pour Cordova et Ionic :
$ npm install -g ionic cordova

Démarrons le projet (basé sur le template « sidemenu », mais il y en a d’autres) :
$ ionic start ioNious sidemenu

À ce stade, nous avons un répertoire contenant le patron minimum de notre application :

$ tree ioNious -d -L 2
.
├── hooks
│   └── after_prepare
├── node_modules
├── platforms
│   └── android
├── plugins
│   ├── cordova-plugin-console
│   ├── cordova-plugin-device
│   ├── cordova-plugin-splashscreen
│   ├── cordova-plugin-statusbar
│   ├── cordova-plugin-whitelist
│   └── ionic-plugin-keyboard
├── resources
├── scss
└── www
   ├── css
   ├── img
   ├── js
   ├── lib
   └── templates

On y retrouve :
 des fichiers de configuration pour les outils : bower.json, gulpfile.js, package.json
 des fichiers de configuration pour cordova : config.xml, ionic.project

Nos fichiers sources sont contenus dans les répertoires scss et surtout www.

C’est dans ce dernier que nous allons développer l’application web qui deviendra grâce à Ionic et Cordova une application hybride pour Android ou iOS.

Il faudra ensuite installer les paquets NPM dont le projet Ionic a besoin. Il suffira d’exécuter la commande $ npm install pour que le dossier node_modules soit créé à la racine du projet. Avec sa configuration initiale, il en installera certains par défaut (listés dans le package.json).

Observons à quoi ça ressemble :

$ cd ioNious
$ ionic serve

Et voilà ! Nous pouvons d’ores et déjà intéragir avec notre application. Et la page se met à jour automatiquement lorsque nous effectuons des changements dans nos templates, nos scripts ou nos feuilles de style. Cool non ?

P.I. sachez qu’il est possible d’installer des plugins Cordova, comme la gestion de la caméra, depuis Ionic via la commade $ ionic plugin add cordova-plugin-camera mais nous n’en aurons pas besoin ici pour notre projet.

Ensuite pour pouvoir déployer sur notre smartphone il nous faudra installer les outils Android.

Le plus simple est peut-être d’installer Android Studio qui se chargera d’installer tout le nécessaire (Android SDK, Platform Tools. Build Tools. …).

Sous Ubuntu on passera par un dépot PPA (cf. https://paolorotolo.github.io/android-studio) :

$ sudo apt-add-repository ppa:paolorotolo/android-studio
$ sudo apt-get update
$ sudo apt-get install android-studio
$ /opt/android-studio/bin/studio.sh (pour lancer les téléchargements et mises-à-jour)

P.I. pour pouvoir tester / déboguer sur le smartphone connecter en USB, cf. https://developer.android.com/studio/run/device.html

P.I. pour moi la version 2.1.0 d’Android Studio fonctionne avec l’OpenJDK-7

P.I. avant de lancer les commandes suivantes :
 Positionner export ANDROID_HOME=~/Android/Sdk/
 Lancer ~/Android/Sdk/tools/android pour :

  • installer SDK Platform Android, SDK Platform Tools, SDK Build tools en version d’API 23
  • enlever ce qui concerne l’API 24
    (j’ai un peu galéré mais ça a finit par marcher…)

Une fois l’environnement prêt, nous pouvons rajouter la plateforme désirée :

$ ionic platform add android

Pour packager :

$ ionic build android

Pour déployer sur un périphérique de test, un smartphone Android par exemple :

$ ionic run android

Cette commande lancera la version debug de notre application sur le premier périphérique trouvé (adb devices), et si aucun n’est trouvé sur un émulateur.

P.I. notez que l’APK généré dans platforms/android/build/outputs/apk/android-debug.apk peut être envoyé sur n’importe quel smartphone Android.

Développement

On commence par le point d’entrée : index.html

<!DOCTYPE html>
<html>
 <head>
   <meta charset="utf-8">
   <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
   <title>ioNious</title>

   <link href="lib/ionic/css/ionic.css" rel="stylesheet">
   <link href="css/style.css" rel="stylesheet">

   <!-- ionic/angularjs js -->
   <script src="lib/ionic/js/ionic.bundle.js"></script>

   <!-- cordova script (this will be a 404 during development) -->
   <script src="cordova.js"></script>

   <!-- your app's js -->
   <script src="js/app.js"></script>
 </head>

 <body ng-app="ionious" ng-controller="NwsCtrl">
   <ion-nav-view></ion-nav-view>
 </body>
</html>

Rien de bien particulier :
 les « includes » par défaut du framework Ionic,
 la déclaration de l’app Angular (ng-app="ionious"),
 Et, l’association de notre controlleur (ng-controller="NwsCtrl")

La balise <ion-nav-view></ion-nav-view> est là pour recevoir nos templates.

Le code js/app.js qui lie tout ça (« Data Binding ») :

// Ionic ioNious app.

(function(){
   'use strict';

   // Revealing pattern:
   angular.module('ionious', ['ionic'])
       .run(runFnct)
       .config(confFnct)
       .controller('NwsCtrl', ['$scope', '$http', NwsCtrl])

   // Startup function.
   function runFnct($ionicPlatform) {
       $ionicPlatform.ready(function() {
           // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard for form inputs)
           if (window.cordova && window.cordova.plugins.Keyboard) {
               cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
               cordova.plugins.Keyboard.disableScroll(true);
           }
           if (window.StatusBar) {
               // org.apache.cordova.statusbar required
               StatusBar.styleDefault();
           }
       });
   }

   // Config function.
   function confFnct($stateProvider, $urlRouterProvider) {
       $stateProvider
           .state('app', {
               url: '/app',
               abstract: true
           })
           .state('nws', {
               url: '/nws',
               templateUrl: 'templates/nws.html'
           })

       // If none of the above states are matched, use this as the fallback
       $urlRouterProvider.otherwise('/nws');
   }

   // Main controller.
   function NwsCtrl($scope, $http) {
       $scope.news = [];

       var url = "http://rss2json.com/api.json?rss_url=http://www.nextinpact.com/rss/news.xml"

       $http.get(url)
           .then(function(response) {
               $scope.news = response.data.items;
           });
   }

})() // IIFE

Le plus important à noter :
 Dans la fonction confFnct on définit les « routes » : dans notre cas pour l’instant on est redirigé vers le templates news.html
 Et, la définition de notre controller qui contiendra toute notre logique.

Dans le controller NwsCtrl, la variable $scope contiendra un tableau news[] qui acceuillera les nious de Next INpact (flux RSS transformé en JSON via l’API http://rss2json.com) récupérées par requête Ajax.

Le template de notre page principale : templates/nws.html

<!--ion-view view-title="Nws"-->
<ion-side-menus enable-menu-with-back-views="false">
 <ion-side-menu side="left" menu-toggle="left">
   <ion-header-bar class="bar-stable">
     <h1 class="title">Actus</h1>
   </ion-header-bar>
   <ion-content>
     <ion-list>
       <ion-item menu-close href="#/app/nws">News</ion-item>
     </ion-list>
   </ion-content>
 </ion-side-menu>
 <ion-side-menu-content>
   <ion-pane>
     <div class="bar bar-header bar-dark">
       <button menu-toggle="left" class="button button-icon icon ion-navicon"></button>
       <h1 class="title">Next INpact - Actualités</h1>
     </div>
     <ion-content class="has-header has-footer">
       <div class="card" ng-repeat="n in news" ng-click="show=!show">
         <div class="item item-divider">
           {{n.title}}
         </div>
         <div class="item item-image">
           <img ng-src="{{n.enclosure.link}}" width="300">
         </div>
         <div class="item item-text-wrap" ng-if="show">
           {{n.description}}
         </div>
       </div>
     </ion-content>
     <div class="bar bar-footer bar-dark">
       <div class="title">Created w/ Ionic</div>
     </div>
   </ion-pane>
 </ion-side-menu-content>
</ion-side-menus>
<!--/ion-view-->

On exploite ici notre « sidemenu » :
 En première partie, le menu avec des liens,
 Puis, le corps de page contenant la boucle sur les articles récupérés.

P.I. on notera que l’on a rajouté sur l’article un événement sur le « click » permettant d’afficher / cacher le texte. De même remarquez que grâce aux directives « menu-toggle » et « menu-close » le menu slide à gauche automatiquement :)

Et voilà le résultat :

En quelques lignes on a déjà une application opérationnelle !

Evolutions

Rajout d’un filtre de recherche. Pour cela il suffit de rajouter dans le template :

     <ion-content class="has-header has-footer">
       <div class="list card">
         <div class="item item-input item-stacked-label">
           <label class="input-label">
             <input type="text" placeholder="Votre recherche" ng-model="search">
             <!--p class="subdued">{{search}}</p-->
           </label>
         </div>
       </div>
       <div class="card" ng-repeat="n in news | filter:search track by $index" ng-click="show=!show">
         ...

Et la magie opère !!!

Vous avez surement ramarqué qu’il y a un temps de latence lors du chargement des nious, aussi on propose d’incérer un écran de Splash screen pour fair patienter…

Pour cela, dans les routes js/app.js, on rajoute notre écran de splash par défaut :

   function confFnct($stateProvider, $urlRouterProvider) {
       $stateProvider
           .state('app', {
               url: '/app',
               abstract: true
           })
           .state('splash', {
               url: '/splash',
               templateUrl: 'templates/splash.html'
           })
           .state('nws', {
               url: '/nws',
               templateUrl: 'templates/nws.html'
           })

       // If none of the above states are matched, use this as the fallback
       $urlRouterProvider.otherwise('/splash');
   }

Le nouvelle page d’acceuil : templates/splash.html

<!--ion-view view-title="Splash"-->
<ion-pane class="io-splash">
 <div class="card">
   <div class="item item-text-wrap">
     <div class="text-center">
       <h2>Bienvenu sur </h2>
       <img src="img/ionious-mini.jpg" alt="" width="300">
       <ion-spinner icon="bubbles" class="padding" ng-if="!loaded"></ion-spinner>
       <a href="#/nws" nav-transition="android" class="button button-block button-positive" ng-if="loaded">Read...</a>
     </div>
   </div>
 </div>
 <div class="bar bar-footer bar-dark">
   <div class="title">Created w/ Ionic</div>
 </div>
</ion-pane>
<!--/ion-view-->

Le splash screen est composée :
 d’un titre + logo
 et d’un spinner qui disparait pour un bouton dès que c’est prêt.

Ne pas oublier de rajouter un flag dans notre controller pour notifier la vue dès que les données sont téléchargées et disponibles :

       $http.get(url)
           .then(function(response) {
               $scope.news = response.data.items;
               $scope.loaded = true;
           });

Gestion du « offline »

Lorsque l’application est « offline » on propose de présenter un contenu préalablement stocké au format JSON (dans le controlleur) :

var url = (navigator.onLine) ? "http://rss2json.com/api.json?rss_url=http://www.nextinpact.com/rss/news.xml" : "data/news.json";

P.S. on pourrait imaginer que ce fichier local est mis à jour à chaque rechargement du flux RSS…

À suivre…

 Rajout en favoris d’articles que l’on aime bien ou à « lire plus tard » ;
 Utilisation du localStorage pour recharger les favoris entre chaque redémarrage de l’appli ;
 « Pull to refresh » ;
 Chargement du menu et des flux RSS via un fichier JSON ;
 Partage via e-mail ;
 D’autres idées… ?

Ci-joint l’archive du projet sous license AGPL :)

ioNious-www-20160918.tar.gz