Ionic + Angular + TypeScript – Configurando un proyecto base

ionic2

Hoy en día tenemos muchas herramientas para poder crear aplicaciones multiplataforma, según lo que nos haga ser más productivos, podemos escoger diferentes entornos. Este es el caso de Xamarin si quieres trabajar con C# y XAML o Apache Cordova si quieres trabajar con HTML5, ambos integrados por defecto en Visual Studio 2015.

En este artículo vamos a centrarnos en la configuración de la parte de HTML5 utilizando las siguientes tecnologías:

  • TypeScript: Un lenguaje de programación opensource que nos ayuda a trabajar con JavaScript de una manera realmente orientada a objetos.
  • Angular: Un framework de JavaScript que nos ayudará a tener un proyecto que orientaremos a MVC.
  • Ionic: Otro framework de JavaScript que nos ayudará a orientar la UI de nuestra app para dispositivos móviles.
  • Gulp: Una herramienta que permite automatizar tareas basado en NodeJS con el que vamos a trabajar más a gusto. Se puede hacer cosas como procesar CSS, minificar el código o cualquier cosa que queramos.
  • Sass: Un lenguaje de programación con el que generaremos nuestro css.

Creando el proyecto

Esta parte la puede hacer cada uno cómo prefiera, a mí me gusta partir de una solución vacía y añadir las otras soluciones necesarias.

Creamos una solución en blanco:

Abrimos Visual Studio 2015, pulsamos en nuevo proyecto y seguimos lo que vemos en la siguiente imagen.

image

Ahora añadimos dos proyectos a la solución, uno de Apache Cordova con TypeScript y otro para Gulp.

image

image

Una vez tenemos estos dos proyectos creados deberíamos ver lo siguiente en nuestro panel:

 image

Ya hemos configurado los proyectos necesarios, ahora podemos empezar a trabajar.

Configurando Gulp

Igual que Grunt, este se basa en configurar una serie de tareas que vamos a automatizar, yo suelo usar uno que voy modificando según el proyecto o ampliando si se hace más complejo, este sería el packages.json donde instalo todas las dependencias de mi proyecto.

    1: {
    2:   "dependencies": {},
    3:   "devDependencies": {
    4:     "gulp": "*",
    5:     "gulp-load-plugins": "*",
    6:     "gulp-clean": "~0.2.4",
    7:     "gulp-htmlbuild": "*",
    8:     "gulp-concat": "*",
    9:     "gulp-clone": "*",
   10:     "gulp-rename": "*",
   11:     "gulp-inject": "*",
   12:     "gulp-minify-css": "*",
   13:     "gulp-minify-html": "~0.1.0",
   14:     "node-sass": "1.1.4",
   15:     "gulp-sass": "1.2.4",
   16:     "gulp-autoprefixer": "*",
   17:     "gulp-uglify": "*",
   18:     "gulp-util": "*",
   19:     "gulp-usemin": "~0.3.2",
   20:     "run-sequence": "~1.0.1"
   21:     
   22:   },
   23:   "main": "gulpfile.js",
   24:   "author": {
   25:     "name": "Pablo Yglesias"
   26:   },
   27:   "contributors": [
   28:     {}
   29:   ]
   30: }

Instalarlos es tan fácil cómo clic derecho > instalar.

image

Y esta es mi configuración típica de Gulp, donde normalmente vamos a utilizar la tarea ‘default’ y al acabar la tarea ‘build’ este sería el código adaptado a este proyecto, aunque personalmente creo que cada uno puede hacerse su propio Gulp de manera que trabaje lo más cómodo posible.

    1: var gulp = require('gulp');
    2: var plugins = require('gulp-load-plugins')();
    3:  
    4: var files = {
    5:     build: 'build/**/*.*',
    6:     buildJS: 'build/code.js',
    7:     buildCSS: 'build/styles.css',
    8:     css: 'css/**/*.css',
    9:     filePaths: ['css/**/*.css', 'ts/Services/*.js', 'ts/Models/*.js', 'ts/Interfaces/*.js', 'ts/Controllers/*.js', 'ts/App.js'],
   10:     index: 'index.html',
   11:     indexBkp: 'index.html.bkp',
   12:     scss: 'scss/*.scss',
   13:     scssAll: 'scss/**/*.scss'
   14: };
   15:  
   16: var paths = {
   17:     build: 'build',
   18:     css: 'css',
   19:     project: '../PYApp/',
   20:     scss: 'scss'
   21: };
   22:  
   23: function getCorrectPaths(folder, files) {
   24:     var cfiles = [];
   25:     for (var i = 0; i < files.length; i++) {
   26:         cfiles.push(folder + files[i]);
   27:     }
   28:     
   29:     return cfiles;
   30: }
   31:  
   32: // Compile Sass
   33: gulp.task('sass', function(){
   34:     return gulp.src(paths.project + files.scss)
   35:         .pipe(plugins.sass())
   36:         .pipe(plugins.autoprefixer({
   37:             browsers: ['last 2 version', 'android 4']
   38:         }))
   39:         .pipe(gulp.dest(paths.project + paths.css));
   40: });
   41:  
   42: // Inject JS & CSS Files
   43: gulp.task('inject', function() {
   44:     return gulp.src(paths.project + files.index)
   45:         .pipe(plugins.inject(
   46:             gulp.src(getCorrectPaths(paths.project, files.filePaths), { read: false }),
   47:             {
   48:                 transform: function (filepath) {
   49:                     if (filepath.indexOf('.js') > -1) {
   50:                         return '<script src="' + filepath.replace(paths.project, '').substring(1) + '"></script>'
   51:                     }
   52:                     // Css
   53:                     return ' <link rel="stylesheet" href="' + filepath.replace(paths.project, '').substring(1) + '">'
   54:                 }
   55:             }
   56:         ))
   57:         .pipe(gulp.dest(paths.project));
   58: });
   59:  
   60: // Celan specific folders
   61: gulp.task('clear', function () {
   62:  
   63:     // If exist indexBkp replace normal index and delete this
   64:     gulp.src(paths.project + files.indexBkp)
   65:         .pipe(plugins.rename('default.html'))
   66:         .pipe(gulp.dest(paths.project)); 
   67:  
   68:     return gulp.src(paths.project + files.build, { read: false })
   69:        .pipe(plugins.clean({ force: true }));
   70: });
   71:  
   72: // Build Files
   73: gulp.task('buildFiles', function() {
   74:     // Save index
   75:     gulp.src(paths.project + files.index)
   76:         .pipe(plugins.clone())
   77:         .pipe(plugins.rename('default.html.bkp'))
   78:         .pipe(gulp.dest(paths.project));
   79:  
   80:     // Build files
   81:     gulp.src(paths.project + files.index)
   82:         .pipe(plugins.usemin(
   83:             {
   84:                 css: [plugins.minifyCss()],
   85:                 js: [plugins.uglify()]
   86:             }
   87:         ))
   88:         .pipe(gulp.dest(paths.project));
   89:     
   90:     gulp.src(paths.project + files.buildJS)
   91:         .pipe(plugins.uglify())
   92:         .pipe(gulp.dest(paths.project + paths.build));
   93:  
   94:     return gulp.src(paths.project + files.buildCSS)
   95:         .pipe(plugins.minifyCss())
   96:         .pipe(gulp.dest(paths.project + paths.build));
   97: });
   98:  
   99: // Init watch
  100: gulp.task('watcher', function () {
  101:     gulp.watch(files.js, ['inject']);
  102:     gulp.watch(files.scssAll, ['sass', 'inject']);
  103: });
  104:  
  105: gulp.task('default', ['clear', 'inject', 'sass']);
  106: gulp.task('build', ['default', 'buildFiles']);
  107: gulp.task('watch', ['watch']);
  108:  

Vale, ya tenemos configurado todo el entorno del gestor de tareas (más adelante modificaremos el index.html para que termine de adaptarse) y si queremos ejecutar una tarea es tan fácil cómo usar Task Runner que es un panel que viene en Visual Studio.

image

Instalando las dependencias necesarias

Vamos a necesitar instalar varios paquetes nuget para poder trabajar, son los siguientes:

  • Angular.UI.UI-Router
  • angularjs
  • angularjs.TypeScript.DefinetelyTyped
  • angular-ui.Typescript.DefinetelyTyped
  • cordova.TypeScript.DefinetelyTyped
  • cordova-ionic.Typescript.DefinetelyTyped
  • ionic

Puedes encontrarlos fácilmente en el gestor de nuget.

image

Generando un proyecto base

Por último vamos a tener un pequeño proyecto base, para comprobar que todo funciona correctamente y además lo podemos usar luego para empezar con nuestra app.

index.html

En este fichero podemos ver dónde va a incluir Gulp los css y los js.

    1: <head>
    2: ...
    3:     <!-- build:css build/styles.css -->
    4:     <!-- inject:css -->
    5:      <link rel="stylesheet" href="css/main.css">
    6:     <!-- endinject -->
    7:     <!-- endbuild -->
    8: ...
    9: </head>
   10:  
   11: <body>
   12: ...
   13:     <!-- build:js build/code.js -->
   14:     <!-- inject:js -->
   15:     <script src="ts/App.js"></script>
   16:     <!-- endinject -->
   17:     <!-- endbuild -->
   18: </body>

También añado mis dependencias para el proyecto, en este caso cordova.js, ionic.bundle (angular + ionic) y un js por si hay que sobrescribir código según la plataforma.

    1: <body>
    2: ...
    3: <!-- JS dependencies -->
    4: <script src="cordova.js"></script>   1:     2: <script src="scripts/ionic.bundle.min.js">   1: </script>   2: <script src="scripts/platformOverrides.js"></script>
    5: ...
    6: </body>

El último punto a destacar es que nos hace falta marcar el contenedor de nuestra app y el contenedor de vistas.

    1: <body ng-app="App">
    2: <ion-nav-view></ion-nav-view>
    3: ...

En conjunto, todo el código anterior queda así.

    1: <!DOCTYPE html>
    2: <html>
    3: <head>
    4:     <meta charset="utf-8" />
    5:     <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    6:     <title>App</title>
    7:  
    8:     <!-- build:css build/styles.css -->
    9:     <!-- inject:css -->
   10:      <link rel="stylesheet" href="css/main.css">
   11:     <!-- endinject -->
   12:     <!-- endbuild -->
   13: </head>
   14: <body ng-app="App">
   15:  
   16:     <ion-nav-view></ion-nav-view>
   17:  
   18:     <!-- JS dependencies -->
   19:     <script src="cordova.js"></script>   1:     2:     <script src="scripts/ionic.bundle.min.js">   1: </script>   2:     <script src="scripts/platformOverrides.js">   1: </script>   2:     3:     <!-- build:js build/code.js -->   4:     <!-- inject:js -->   5:     <script src="ts/App.js"></script>
   20:     <!-- endinject -->
   21:     <!-- endbuild -->
   22: </body>
   23: </html>

Import.ts

Por convenio, es el fichero donde importamos los ficheros de typescript necesarios en nuestro proyecto, y este fichero es importado en todos nuestros ficheros de typescript, de manera que tenga acceso a todos los que me interesan mientras trabajo.

    1: // Vendor
    2: /// <reference path="../scripts/typings/angularjs/angular.d.ts" />
    3: /// <reference path="../scripts/typings/cordova/cordova.d.ts"/>
    4: /// <reference path="../scripts/typings/ionic/ionic.d.ts" />
    5: /// <reference path="../scripts/typings/cordova-ionic/cordova-ionic.d.ts" />
    6: /// <reference path="../scripts/typings/angular-ui/angular-ui-router.d.ts" />
    7:  
    8:  
    9: // App
   10: /// <reference path="app.ts" />
   11:  
   12: // Models
   13:  
   14: // Interfaces
   15:  
   16: // Controllers
   17:  
   18: // Directives
   19:  
   20: // Services
   21:  
   22: // Other

App.ts

He generado una app en Angular y Typescript con el $stateprovider para manejar las diferentes partes, de momento sin controladores ya que los añadiré más adelante, concretamente creamos un módulo en TS que será el global de nuestra app y otro (que no son el mismo) para angular el cual tiene como dependencia ionic.

    1: /// <reference path="imports.ts" />
    2:  
    3: module App {
    4:     'use strict';
    5:  
    6:     angular.module('App', ['ionic'])
    7:         .config(['$stateProvider', '$urlRouterProvider', states])
    8:  
    9:     function states($stateProvider: ng.ui.IStateProvider, $urlRouterProvider: ng.ui.IUrlRouterProvider) {
   10:         $urlRouterProvider.otherwise('/main');
   11:  
   12:         $stateProvider
   13:             .state('main', {
   14:                 url: '/main',
   15:                 templateUrl: '../templates/main.html'
   16:             })
   17:             .state('sample', {
   18:                 url: '/sample',
   19:                 templateUrl: '../templates/sample.html'
   20:             })
   21:  
   22:     }
   23: }

Las plantillas són básicamente dos botones temporales para poder navegar entre las páginas y ver que todo va correctamente.

main.html

    1: <ion-view view-title="Home">
    2:     <ion-header-bar class="bar-dark bar bar-header">
    3:         <h1 class="title">Main</h1>
    4:     </ion-header-bar>
    5:  
    6:     <ion-content>
    7:         <div class="list">
    8:             <div class="item item-divider">
    9:                 <a href="#/sample">Go to sample page!</a>
   10:             </div>
   11:         </div>
   12:     </ion-content>
   13: </ion-view>

sample.html

    1: <ion-view view-title="Sample">
    2:     <ion-header-bar class="bar-dark bar bar-header">
    3:         <h1 class="title">Sample</h1>
    4:     </ion-header-bar>
    5:  
    6:     <ion-content>
    7:         <div class="list">
    8:             <div class="item item-divider">
    9:                 <a href="#/">Go to home page!</a>
   10:             </div>
   11:         </div>
   12:     </ion-content>
   13: </ion-view>

Si hemos seguido todos los pasos correctamente deberíamos poder ejecutar la tarea default o watch de Gulp mediante el task runner, ponernos en modo Debug y lanzar cualquier emulador, por ejemplo el de Ripple.

image

Otra manera  para hacerlo es clicar con el botón derecho en nuestro proyecto > Debug > Start new instance.

image

task

_____________________________

Quique Fernández

Technical Evangelist Intern

@CKGrafico