Archivos de la categoría Trabajo

Ionic 4+ ErrorHandler, Cómo enviar los errores a un servicio externo

Cuando tu app da error y se ejecuta en dispositivos en los que no tienes acceso a los logs, esta solución te puede solucionar parte del camino para resolver el error.

Primero creamos el servicio que enviara el error a un servidor o servicio externo, en este ejemplo yo hago una petición HTTP a una URL externa, pero puede ser todo lo complejo que quieras:

error-log-service.ts

import { Injectable } from '@angular/core';

import { HttpClient, HttpHeaders} from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class ErrorLogService {

  constructor(
    private http: HttpClient
  ){ }

  sendError(error) {

    const headers: HttpHeaders = new HttpHeaders({
      'Content-Type': 'application/json'
    });

    const requestOptions = { headers };

    const url = 'https://www.javray.com/log.php';

    const body: any = {};

    body.message = error.message || 'N/A';
    body.stack = error.stack || 'N/A';

    this.http.post(url, body, requestOptions).subscribe();
  }
}

Después definimos la clase que extenderá ErrorHandler y que hará uso de nuestro servicio:

my-error-handler.ts

import { Injectable, ErrorHandler } from '@angular/core';

import { ErrorLogService } from './error-log.service';

@Injectable({
  providedIn: 'root'
})
export class MyErrorHandler extends ErrorHandler {

  constructor(private errorLogService: ErrorLogService) {
    super();
  }

  handleError(error) {
    super.handleError(error);
    this.errorLogService.sendError(error);
  }
}

Y por ultimo añadimos MyErrorHandler como provider en la app:

app.module.ts


import { ErrorHandler, NgModule, Injectable } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { HttpClientModule } from '@angular/common/http';

import { MyErrorHandler } from './my-error-handler';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, HttpClientModule],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: ErrorHandler, useClass: MyErrorHandler },
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Una implementación sencilla del servicio que recibe los logs sería la siguiente:

log.php


header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Origin: *');

if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
        if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
        if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
        exit(0);
}

$params = json_decode(file_get_contents('php://input'), true);

$fecha = date('Y-m-d H:m:s');

$log = '[' . $fecha . '] ';
$log .= '"' . $params['message'] . '" ';
$log .= $params['stack'];

$fichero = '/var/www/html/logs/' . date('Ymd') . '.log';

file_put_contents($fichero, $log . "\n", FILE_APPEND);

echo json_encode(array('estado' => 'OK'));

Errores en PWA en Ionic

Si creas las versión de producción de un proyecto web hecho en Ionic:

ionic build --prod

y al probarlo en un emulador, o en cualquier dispositivo que use como URL file://, te da el siguiente error:

Failed to load module script: The server responded with a non-JavaScript MIME type of “”. Strict MIME type checking is enforced for module scripts per HTML spec.

La solución es editar el archivo tsconfig.json y cambiar el target de “target": "es2015" a "target": "es5".

Solución encontrada aquí:

https://stackoverflow.com/a/56994197

Y si la primera vez que lo pruebas no se ve nada y te dice que no encuentra ninguno de los ficheros css y js, la solución es cambiar en el index.html la etiqueta base de <base href=”/” /> a <base href=”./” />

Cómo crear una cookie mediante un RewriteRule de Apache

Me ha tocado hacer un tarea bastante sencilla a simple vista:

Crear una cookie con el nombre del usuario que se ha autenticado en una página restringida de Apache.

Yo ya sabía que con el el RewriteEngine puedes leer las cookies y redirigir a una página a otra, pero lo de crear una cookie me totalmente desconocido.

Así que después de un rato buscando por Google, llego a esta página:

https://www.askapache.com/htaccess/htaccess-fresh/

En ella encuentro el código que pone una cookie mediante RewriteRule:

RewriteEngine On
RewriteBase /
RewriteRule ^(.*)(de|es|fr|it|ja|ru|en)/$ - [co=lang:$2:.askapache.com:7200:/]

Nos tenemos que fijar en la parte que va entre corchetes que es donde se pone la cookie:

[co=NOMBRE_COOKIE:VALOR:DOMINIO:VIGENCIA:PATH]

La ristra completa de parámetros es la siguiente:

[co=NOMBRE_COOKIE:VALOR:DOMINIO:VIGENCIA:PATH:SECURE:HTTPONLY]

En esta página se encuentra la definición de cada uno de ellos:

https://httpd.apache.org/docs/2.4/rewrite/flags.html

Juntando todo tenemos que la solución para poner una cookie con el usuario es la siguiente:

 RewriteEngine On
 RewriteBase /
 RewriteCond %{REMOTE_USER} !^$ [NC]
 RewriteRule .* - [co=user:%{REMOTE_USER}:.javray.com:7200:/]

cordova, añadir propiedades al tag activity del AndroidManifest.xml

Recientemente he tenido que añadir algunas propiedades al Activity que crea cordova. Si os fijáis en el fichero AndroidManifest.xml, el cual se genera automáticamente cada vez que ejecutamos un cordova build android o un cordova run android, veréis que tiene un tag activity el cual tiene las propiedades con las que se ejecuta el Activity.


...
        
            
                
                
            
        
    ...


Imaginemos que lo queremos añadir al el atributo android:excludeFromRecents=”true”, como el fichero AndroidManifest.xml se genera dinámicamente, si lo modificamos directamente, al generar el apk se sobrescribirá y perderemos la modificación.

Por tanto tendremos que recurrir a la siguiente etiqueta en el config.xml del proyecto.



    

Lo interesante de la etiqueta edit-file es el atributo mode, que en este caso toma el valor de merge, lo cual significa que no tenemos que poner el tag activity todos los atributos, sino que solo los que queremos añadir y estos se añadirán a los ya existentes quedando de la siguiente manera:


...

            
                
                
            
        
...

Ionic 2 y Angular 2 – Tercera parte

Después de la primera y la segunda parte, ahora nos toca esta tercera en la que crearemos un servicio para que nuestra app puede obtener datos de manera sencilla.

Lo primero es declarar el servicio y nuestra clase Query del capítulo anterior en el fichero app/app.module.ts.

Primero importamos los módulos:


import { ApiService } from '../services/api.service';
import { Query } from '../services/query';

Y después los añadimos dentro del array de providers del @NgModule:

 providers: [
    StatusBar,
    SplashScreen,
    Query, // <--
    ApiService, // <--
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]

Una vez hecho esto vamos a crear el archivo services/api.service.ts donde lo primero que haremos será importar nuestra clase Query y por otro lado importar la clase Injectable para que podamos usar este servicio en otras partes de la app.

import { Injectable } from '@angular/core';
import { Query } from './query';

A continuación defino la función que hará uso de la clase Query y un mapper para mis datos:

const doQuery = (query, params, mapper) => {
  return query.post('https://miapi.com/v1/', params, mapper);
};

const mapper = data => {
  data.var3 = data.var1 + data.var2;
};

Si no queremos procesar los datos que recibimos y nos sirven tan cual llegan la función de mapper quedaría de la siguiente forma:

  const mapper = data => data;

Y por último defino la clase del servicio en sí.

@Injectable()
export class ApiService {

  constructor(private query: Query) {
    this.query = query;
  }

  metodo1() {
    return doQuery(this.query, {}, mapper);
  }

  metodo2({param1, param2}) {
    return doQuery(this.query, {param1, param2}, mapper);
  }
}

En lo que hay que fijarse en esta clase es la llamada al constructor, la cual recibe como parámetro query que es de tipo Query y que ésta se asigna al objeto this, que es la clase en sí para poder usarla luego dentro de los distintos métodos.

Aquí tenéis el código completo del servicio:

import { Injectable } from '@angular/core';
import { Query } from './query';

const doQuery = (query, params, mapper) => {
  return query.post('https://miapi.com/v1/', params, mapper);
};

const mapper = data => {
  data.var3 = data.var1 + data.var2;
};

@Injectable()
export class ApiService {

  constructor(private query: Query) {
    this.query = query;
  }

  metodo1() {
    return doQuery(this.query, {}, mapper);
  }

  metodo2({param1, param2}) {
    return doQuery(this.query, {param1, param2}, mapper);
  }
}

En la próxima parte veremos cómo crear una página, utilizar el servicio y mostrar los datos recibidos.

Ionic 2 y Angular 2 – Segunda parte

Si quieres saber de que va esto, lee la primera parte.

La siguiente habilidad o conocimiento que tenía que obtener era saber llamar a una API externa para obtener unos datos.

Antes de crear un servicio, lo cual abordaré en la tercera parte, hay que saber lo más básico de todo: ¿cómo puedo hacer una llamada HTTP a una URL?

Para ello tiraremos de Angular y del módulo http:

import {Http} from '@angular/http';

Como mi idea desde el principio era hacer todo modular y poder abstraerme de las llamadas, lo que hice fue crear un componente que llamé Query y que será el que utilice para realizar las llamadas a la API externa:

import { Injectable } from '@angular/core';
import {Http} from '@angular/http';

@Injectable()
export class Query {

  constructor(private http: Http) {
    this.http = http;
  }

  get(url, mapper) {
    return this.http.get(url).map(data => mapper(data));
  }

  post(url, params, mapper) {
    return this.http.post(url, params).map(data => mapper(data));
  }
}

Dentro de la clase Query he se ven tres métodos:

  • constructor
  • get
  • post

El primero es el constructor de la clase, lo más interesante de él es definir las variables que recibe, las cuales son lo que antes hacíamos antes en ionic 1 al inyectar variables en el constructor del controlador. Por tanto aquí lo que estamos haciendo es definir la variable http que será de tipo Http. Después para poder usarla en el resto de métodos fuera del constructor lo que hacemos es asignarlo a this, que en este caso es la propia instancia de la clase.

Los otros dos métodos son llamadas de tipo GET y POST. Aquí cabe destacar el parámetro mapper, el cual lo voy a usar para formatear los datos que llegan de la API por si quiero hacer una transformación antes de usar los datos en la app.

Con todo esto, la manera de hacer una llamada sería la siguiente:


import { Query } from './query';

export class MiClase {
  constructor(private query: Query) {
    query.get('http://miapi.com/v1/metodo', (e) => e).subscribe(datos => this.datos = datos);
  }
};

Se puede ver en el ejemplo que en vez de usar promesas, ahora Angular usa Observables, que según dicen son mejores que las promesas, por tanto http devuelve un Observable al que hay que subscribirse para poder recoger los datos.

Por hoy ya es suficiente, en la próxima parte crearemos un servicio que haga uso de Query.

Ionic 2 y Angular 2 – Primera parte

Después de un tiempo resistiendome y engañandome a mi mismo de que Ionic con Angular 2 no era lo que yo necesitaba para crear nuevos proyectos. Después de dar mis primeros pasos con React y sobre todo después de asistir a un curso, muy bueno por cierto, de Angular 1.5.X orientado a componentes. Por fin, hace un par de semanas, le di una oportunidad a la nueva versión, reescrita desde cero y con Typescript, de Ionic.

Para probar cómo funcionaba y qué concimientos iba a necesitar para en un futuro no muy lejano implementar una app para algún cliente, vi que necesitaba adquirir unos conceptos básicos que me permitieran desarrollar desde cero una app sencilla con unas pocas pantallas, un par de listas, almacenamiento local y llamadas a una API externa para obtener contenido.

Por todo ello, voy a escribir una serie de artículos en los cuales voy a ir contando qué componentes básicos he usado, cómo he hecho la app y qué pasos he seguido para adquirir todos los conocimientos necesarios para crear una app con Ionic.

En esta primera parte voy a hablar de la estructura de la app, es decir de cómo organizar el proyecto para que luego cuando haya que ir añadiendo funcionalidad no tengas todo desperdigado y tengas que repetir código una y otra vez.

Lo primero que hice, aunque parezca muy básico, fue seguir la página de Get started,  con lo que instale ionic y cordova, y escogí la plantilla de tabs para crear mi app:

npm install -g cordova ionic

ionic start myApp tabs

Una vez hecho esto observé que la estructura directorios había cambiado un poco, para empezar ya no metes el código en www, sino que existe un directorio src en el cual metes todo el código de la app, antes tenías por un lado el los js en www/js y las plantillas en www/templates, ahora como estás orientando tu programación a componentes tienes en src un directorio pages que a su vez contiene un directorio por cada componente y dentro de estos últimos, tres archivos: plantilla, estilos y javascript(typescript):

home.html home.scss home.ts

Seguimos analizando y vemos una carpeta app en la que tenemos los mismo elementos que en una página pero la parte de funcionalidad dividida en componente, modulo y principal:

app.component.ts	app.module.ts		main.ts
app.html		app.scss

Esto se correspondería con el archivo app.js que teníamos antes y donde inyectábamos todos los módulos necesarios para la app, así como la configuración de la app y la definición de las rutas, y alguna que otra directiva.

El archivo app.component.ts, define el componente principal sobre el cual se va construir la app, yo lo he dejado tal y como estaba y no me ha hecho falta modificarlo.

En cambio el archivo app.module.ts, lo he tenido que tocar y mucho ahí es donde tienes que importar todos los componentes que vayas a usar en la app, tanto páginas completas, como partes de estas, así como servicios.

Dentro del NgModule hay 5 apartados:

  • declarations,
  • imports
  • bootstrap
  • entryComponents
  • providers

En declarations van el componente de la app, así como todos los componentes de páginas y tabs, imports y bootstrap no los he tocado. en entryComponents vuelve a estar la páginas y los tabs. Y en providers he añadido los servicios que he ido creando.

main.ts, sinceramente ni había mirado lo que tenía dentro, así que se queda sin modificar.

Con todo esto me di cuenta que la estructura que crea Ionic por defecto no está mal y se puede usar para desarrollar una app medianamente compleja, el único añadido que he hecho ha sido crear a la altura de pages un directorio services que es dónde van a ir todos los servicios de la app.

En la siguiente parte hablaré de cómo hacer llamadas a una API externa para obtener los datos que queremos visualizar en la app.

 

Yeoman, Angular, ng-table y html-minifier

Últimamente cada vez que tengo que hacer un aplicación web suelo implementarla usando Angular. Para construir la estructura y automatizar tareas me apoyo en Yeoman, que es una herramienta muy útil para el desarrollo web.

El tema es que estaba yo con mi proyecto en angular, usando ng-table, un módulo que permite de manera sencilla usar tablas con filtros, ordenación, paginación, etc.

Todo funcionaba muy bien hasta que he hecho un grunt build del proyecto y al subir la versión de distribución del mismo no me funcionaba la parte de ordenación de tablas.

Al hacer un grunt build, yeoman, crea una versión minimizada y comprimida tanto de los css, como los js y el html del proyecto. He pensando que en alguna parte del proceso algo estaba mal y me dejaba sin esa funcionalidad.

Después de ver que no era por algo que había hecho yo mal, he revisado qué es lo que hace la tarea build dentro del fichero Gruntfile.js. He visto que todas las vistas del proyecto se convertían a javascript y se usaba $templateCache para las mismas.

En un primer intento, he comentado la subtarea que se encargaba de convertir las vistas en HTML a javascript y he visto que todo funcionaba bien, por tanto el problema está en cómo se hace la conversión.

He visto que primero se minimiza el HTML con html-minifier y analizando el resultado he visto que ese HTML minimizado perdía la parte del código HTML que indicaba al módulo ng-table la ordenación de las columnas.

Me he instalado el html-minifier para usarlo en el Terminal y he visto que opciones usaba la tarea de grunt, concretamente eran las siguientes:

 options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        }

He ido probando una por una las opciones, pero en su versión de Terminal para ver qué salida producían:

html-minifier --collapse-whitespace --conservative-collapse  --collapse-boolean-attributes  main.html 

Y cuando he llegado a la opción de collapseBooleanAttributes, he visto que esa era la opción que transformaba la plantilla y que hacía que dejara de funcionar la ordenación. Concretamente:

<td sortable="'valor'">  => <td sortable>

Por lo que comentando collapseBooleanAttributes: true en el fichero Gruntfile.js he conseguido que funcionara perfectamente la versión de distribución del proyecto.

Angular y rendimiento

Últimamente estoy haciendo muchos proyectos utilizando Angular, bien sea usando solo Angular + Bootstrap o Ionic que está basado en él.

Me suele gustar buscar información sobre buenas prácticas y mejorar el rendimiento de las aplicaciones hechas con dicha tecnología y hoy he encontrado un artículo en el cual con una sola línea se mejora de manera notable el rendimiento en producción de una aplicación hecha con Angular.

La línea en cuestión es la siguiente:

$compileProvider.debugInfoEnabled(false);

Concretamente su uso completo sería el siguiente:

myApp.config(['$compileProvider', function ($compileProvider) {
  $compileProvider.debugInfoEnabled(false);
}]);

Lo he probado en dos proyectos que tengo hechos con Angular y en uno con Ionic, y en los de Angular la mejora ha sido bastante buena, las páginas renderizan mucho más rápido. En Ionic he notado que la fluidez de la app al cambiar de vista mejoraba de manera considerable.

La recomendación de todas formas es que lo actives cuando la aplicación esté en producción así que yo la he usado de la siguiente manera:

// Angular

myApp.config(['$compileProvider', function ($compileProvider) {
  if (document.location.host.indexOf('localhost') === -1) {
    $compileProvider.debugInfoEnabled(false);
  }
}]);

// Ionic

myApp.config(['$compileProvider', function ($compileProvider) {
  if (window.cordova) {
    $compileProvider.debugInfoEnabled(false);
  }
}]);

Aquí dejo la referencia al artículo en cuestión y alguno más que da consejos para mejorar el rendimiento de aplicaciones hechas con Angular:

https://medium.com/swlh/improving-angular-performance-with-1-line-of-code-a1fb814a6476#.ov0lfveqz
http://maxmautner.com/post/135816438020/11-huge-ionic-performance-tips
http://julienrenaux.fr/2015/08/24/ultimate-angularjs-and-ionic-performance-cheat-sheet/