Ionic et ngCordovaMocks

L'un des nombreux avantages du framework ionic est de pouvoir développer son application mobile directement sur son navigateur préféré (Chrome ? ;)).

Dans cet article et de manière général, j'utilise la librairie ngCordova dès que j'ai besoin d'utiliser une fonctionalité native d'un téléphone.

Il y'a cependant une chose qui m'a dérangé dès le début de mon utilisation de ionic et ngCordova :

Cordova plugins do not work while developing in your browser, because each plugin accesses a specific API (such as camera, microphone, accelerometer) which is not available in your browser. Additionally, some plugins don't work in the emulator, such as the Camera plugin, so development on your physical device is required.

Effectivement, c'est logique... mais comment faire pour pouvoir continuer le développement sur navigateur et même faire des tests sans qu'une erreur javascript vienne faire planter tout mon code ?

ngCordovaMocks

Après quelques recherche, j'ai trouvé cet article Introducing ngCordovaMocks de @chadcampbell, cool, une solution à mon problème !

De plus, ngCordovaMocks est inclus dans la version officiel de ngCordova depuis la version 0.1.4-alpha https://github.com/driftyco/ng-cordova/releases/tag/v0.1.4-alpha

Son utilisation est assez simple, après avoir installé ngCordova bower install ngCordova, il y'aura en plus du fichier ng-cordova.js, un fichier ng-cordova-mocks.js.

Ensuite il faut :

  • Inclure le fichier ng-cordova-mocks.js dans notre index.html au lieu ng-cordova.js
  • Changer le module à charger dans notre app.js (ngCordova à remplacer par ngCordovaMocks)

Et là, c'est parfait (ou presque) ! Je peux continuer le développement en mode navigateur tout en ayant mock l'utilisation de fonctionnalité native.

Pour illustrer simplement, je vais prendre l'exemple de l'utilisation du plugin cordovaNetwork pour tester dans mon application si mon téléphone est connecté à un réseau ou non.

Je veux afficher un message "online" ou "offline" en fonction de l'êtat du téléphone.

Au préalable, j'ai créé une app ionic ionic start myApp blank et j'ai installé ngCordova et le plugin cordovaNetwork

www/js/app.js

angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngCordova'])

www/index.html

<!DOCTYPE html>
<html>
<head>
  <script src="lib/ngCordova/dist/ng-cordova.js"></script>
</head>
<body ng-app="starter">
</body>
</html>

www/js/controllers.js

angular.module('starter.controllers', [])

.controller('HomeCtrl', function($scope, $cordovaNetwork) {
  $scope.message = "";
  if($cordovaNetwork.isOnline()){
    $scope.message = "Online";
  }
  else{
    $scope.message = "Offline";
  }
});

www/templates/tab-home.html

<ion-view view-title="Home">
  <ion-content class="padding">
    <p class="text-center">

        {{message}}

    </p>
  </ion-content>
</ion-view>

Sur mon téléphone aucun problème, le message "Online" s'affiche si je suis connecté à un wifi, en 4G, en 3G ou en edge et "Offline" si aucun réseau n'est trouvé.

Sur mon navigateur par contre :

TypeError: Cannot read property 'type' of undefined
    at Object.isOnline (http://localhost:8100/lib/ngCordova/dist/ng-cordova.js:4678:48)
    at new <anonymous> (http://localhost:8100/js/controllers.js:5:22)
    at invoke (http://localhost:8100/lib/ionic/js/ionic.bundle.js:12468:17)
    at Object.instantiate (http://localhost:8100/lib/ionic/js/ionic.bundle.js:12476:27)
    at http://localhost:8100/lib/ionic/js/ionic.bundle.js:16745:28
    at self.appendViewElement (http://localhost:8100/lib/ionic/js/ionic.bundle.js:47716:24)
    at Object.switcher.render (http://localhost:8100/lib/ionic/js/ionic.bundle.js:45973:41)
    at Object.switcher.init (http://localhost:8100/lib/ionic/js/ionic.bundle.js:45893:20)
    at self.render (http://localhost:8100/lib/ionic/js/ionic.bundle.js:47590:14)
    at self.register (http://localhost:8100/lib/ionic/js/ionic.bundle.js:47548:10) <ion-nav-view name="tab-dash" class="view-container tab-content" nav-view="active" nav-view-transition="ios">ionic.bundle.js:19890 (anonymous function)

C'est frustrant, car je veux continuer de développer sur mon navigateur pour ensuite tester sur mon téléphone mais cette erreur me bloque, c'est là qu'intervient ngCordovaMocks.

www/js/app.js

angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngCordovaMocks'])

Je charge le module ngCordovaMocks à la place de ngCordovaMock.

www/index.html

<!DOCTYPE html>
<html>
<head>
  <script src="lib/ngCordova/dist/ng-cordova-mocks.js"></script>
</head>
<body ng-app="starter">
</body>
</html>

J'inclus ng-cordova-mocks.js à la place de de ng-cordova.js

Je reload ! et c'est bon, plus d'erreur et je peux continuer mon développement.

Voila une bonne chose de faite, mais maintenant ce qui me dérange c'est de devoir aller modifié ces deux fichiers à chaque fois que je veux changer d'environnement de développement.

Ce qui serait vachement sympa, c'est de pouvoir modifier ces fichiers à l'aide d'une ligne de commande !

Pour ça, je vais utiliser gulp-preprocess qui va me permettre de gérer plusieurs mode de développement.

Si vous ne connaissez pas gulp-preprocess, je vous invite à lire la documentation qui est courte, bien expliquée avec quelques exemples :-)

Gulp plugin to preprocess HTML, JavaScript, and other files based on custom context or environment configuration

npm install gulp-preprocess --save-dev

J'ajoute dans mon gulpfile.js, 2 tâches, une pour le développement sur navigateur et une pour le développement sur téléphone.

gulpfile.js

var preprocess = require('gulp-preprocess');
gulp.task('browser-development', function() {
  gulp.src('./www/gulp_preprocess_me/*.js')
    .pipe(preprocess({context: { NODE_ENV: 'BROWSER-DEVELOPMENT'}}))
    .pipe(gulp.dest('./www/js/'));

  gulp.src('./www/gulp_preprocess_me/index.html')
    .pipe(preprocess({context: { NODE_ENV: 'BROWSER-DEVELOPMENT'}}))
    .pipe(gulp.dest('./www/'));
});


gulp.task('device-development', function() {
  gulp.src('./www/gulp_preprocess_me/*.js')
    .pipe(preprocess({context: { NODE_ENV: 'DEVICE-DEVELOPMENT'}}))
    .pipe(gulp.dest('./www/js/'));

  gulp.src('./www/gulp_preprocess_me/index.html')
    .pipe(preprocess({context: { NODE_ENV: 'DEVICE-DEVELOPMENT'}}))
    .pipe(gulp.dest('./www/'));
});

Je crée le dossier "gulp_preprocess_me" qui va contenir tous les fichiers à "preprocess". Dans notre cas, ça sera l'index.html et l'app.js

/www/gulp_preprocess_me/index.html

<!DOCTYPE html>
<html>
  <head>
    <!-- @if NODE_ENV='DEVICE-DEVELOPMENT' -->
    <script src="lib/ngCordova/dist/ng-cordova.js"></script>
    <!-- @endif -->

    <!-- @if NODE_ENV='BROWSER-DEVELOPMENT' -->
    <script src="lib/ngCordova/dist/ng-cordova-mocks.js"></script>
    <!-- @endif -->  
  </head>
  <body ng-app="starter">
  </body>
</html>

Avec ces 2 conditions, je vais pouvoir inclure le fichier js dont j'ai besoin selon le mode de développement.

  <!-- @if NODE_ENV='DEVICE-DEVELOPMENT' -->
  <script src="lib/ngCordova/dist/ng-cordova.js"></script>
  <!-- @endif -->

  <!-- @if NODE_ENV='BROWSER-DEVELOPMENT' -->
  <script src="lib/ngCordova/dist/ng-cordova-mocks.js"></script>
  <!-- @endif -->

/www/gulp_preprocess_me/app.js

// @if NODE_ENV == 'BROWSER-DEVELOPMENT'
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngCordovaMocks'])
// @endif

// @if NODE_ENV == 'DEVICE-DEVELOPMENT'
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngCordova'])
// @endif

Avec ces 2 conditions, je vais pouvoir charger le bon module selon le mode de développement.

// @if NODE_ENV == 'BROWSER-DEVELOPMENT'
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngCordovaMocks'])
// @endif

// @if NODE_ENV == 'DEVICE-DEVELOPMENT'
angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', 'ngCordova'])
// @endif

Il ne me reste plus qu'a executer la bonne tâche en fonction du mode que je souhaite.

gulp device-development ou gulp browser-development

Avantages :

  • C'est plus pratique de modifier l'index.html et l'app.js à l'aide d'une ligne de commande :-)
  • Ca va plus vite que de modifier les fichiers à la main.
  • On peut envisager d'executer la commande pour le mode "device" dans un script de deploiement pour éviter d'envoyer en production les mocks.

Inconvénients :

  • On est obligé d'avoir une copie de l'index.html et de l'app.js dans le dossier gulp_preprocess_me
  • Si on doit ajouter du code dans 1 de ces 2 fichiers, il faut absolument travailler dans le dossier gulp_preprocess_me, ce qui n'est pas très habituel.

Si vous avez déja eu le problème et que vous avez une autre solution, n'hésitez pas à la partager dans les commentaires !

Vous trouverez une démo ici : ngCordovaMocks Demo

Pierre-Julien D'alberto Aka Goundar, avec lui y'a pas de lézard !