Merida Design Blog

Publicado el | Tutoriales / , ,

Como crear un componente de Angular 2 para input file personalizado, con vista previa y drag & drop

Siguiendo con los artículos sobre como crear un input personalizado, en esta ocasión es el turno para Angular 2 y Typescript.

Si es la primera vez que trabajas con Angular2 y Typescript, probablemente quieras leer primero la Guía de inicio con Angular 2.

Para crear nuestro componente personalizado ya no es necesario crear una directiva como en Angular 1.+, ahora las aplicaciones de Angular se construyen en base a componentes que encapsulan funcionalidad como en el caso de nuestro input personalizado.


Al final de este artículo el resultado será igual a este demo:

Para el demo estoy cargando los archivos desde un CDN, pero para las aplicaciones en producción te recomiendo seguir la Guía de inicio.


El HTML

Crea el archivo index.html y coloca el siguiente código:

<html>
    <head>
        <title>Input File Component for Angular 2</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="https://file.myfontastic.com/SLzQsLcd7FmmzjBYTcyVW3/icons.css" rel="stylesheet">
        <link rel="stylesheet" href="style.css">
        
        <!-- 1. Polyfills para IE -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.33.3/es6-shim.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.16/system-polyfills.js"></script>
        
        <!-- 2. Dependencias -->
        <script src="https://code.angularjs.org/2.0.0-beta.6/angular2-polyfills.min.js"></script>
        <script src="https://code.angularjs.org/tools/system.js"></script>
        <script src="https://code.angularjs.org/tools/typescript.js"></script>
        <script src="https://code.angularjs.org/2.0.0-beta.6/Rx.min.js"></script>
        <script src="https://code.angularjs.org/2.0.0-beta.6/angular2.min.js"></script>
        
        <!-- 3. SystemJS -->
        <script>
            System.config({
                transpiler: 'typescript', 
                typescriptOptions: { emitDecoratorMetadata: true }, 
                packages: {'app': {defaultExtension: 'ts'}} 
            });
            
            System.import('app/main')
                .then(null, console.error.bind(console));
        </script>
    </head>
     
    <body>
        <!-- 4. Componente de la aplicación -->
        <my-app>Cargando...</my-app>
    </body>
</html>

1. Polyfills: Para que la aplicación funcione en versiones antiguas de Internet Explorer es necesario cargar estos archivos.

2. Dependencias: Es necesario cargar estas librerías para que Angular funcione correctamente junto con Typescript.

3. SystemJS: Configuramos el lector de módulos dinámico indicándole que usaremos el transpiler: 'typescript' para interpretar los archivos de Typescript, y con la instrucción import (línea 27) le indicamos el archivo principal que se encarga de iniciar la aplicación.

4. Componente: Colocamos la etiqueta que define el componente de la aplicación y dentro de la cual se cargará toda la aplicación, el contenido de ese componente lo veremos a continuación.


Arrancar la aplicación

Crea una carpeta con el nombre app y dentro de la carpeta crea un archivo con el nombre main.ts y coloca el siguiente código.

Todos los archivos creados a partir de ahora irán dentro de la carpeta app/.

import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';

bootstrap(AppComponent);

Para iniciar la aplicación en Angular 2 debemos ejecutar el método bootstrap pasándole como parámetro un componente (linea 4).


Componente de la aplicación

Crea un archivo con el nombre app.component.ts (.component indica el tipo de módulo) y coloca el siguiente código:

import {Component} from 'angular2/core';
import {FileUploaderComponent} from './file-uploader.component';

@Component({
    selector: 'my-app',
    templateUrl: 'app/app.component.html',
    directives: [FileUploaderComponent]
})
export class AppComponent {}

Como puedes ver en la linea 5, estamos indicando como selector la etiqueta que colocamos anteriormente en el index.html, de esa forma angular sabrá que ahí debe ejecutarse este componente.

En la línea 2 importamos el componente del input file al que llamo FileUploaderComponent, sin embargo, para poder usar este componente dentro de la plantilla del AppComponent es necesario incluirlo en la configuración del componente como una directiva (linea 7).

Por último, en la linea 6 puedes ver que la plantilla la estamos incluyendo desde un archivo externo, vamos a ver el contenido de ese archivo.

Crea un archivo con nombre app.component.html y coloca el siguiente código:

<form>
    <file-uploader [activeColor]="'orangered'" [baseColor]="'lightgray'"></file-uploader>
</form>

Si bien el código es corto y podía colocarse directamente en el archivo del componente, es una buena idea acostumbrarnos a mantenerlos separados para que sea fácil de mantener y extender.

En la plantilla puedes ver la etiqueta file-uploader que corresponde al componente del input file que crearemos a continuación, sin embargo, antes de eso me gustaría que observes los atributos que contiene esa etiqueta ([activeColor] y [baseColor]).

En Angular 2 podemos pasar valores a los componentes a través de atributos declarándolos como se muestra arriba, con el nombre de la variable encerrada en corchetes ([]) y colocando del otro lado de la igualdad el valor, sin embargo, este valor es interpretado como una expresión, es decir, si para el código de arriba en lugar de colocar "'orangered'" colocamos solo "orangered" (sin las comillas simples), Angular interpretará orangered como una variable y no como una cadena de texto.


Componente FileUploader

Muy bien, ahora es momento de ver el componente principal del tutorial.

Componente

Crea un archivo con el nombre file-uploader.component.ts y coloca el siguiente código:

import {Component} from 'angular2/core';

@Component({
    selector: 'file-uploader',
    templateUrl: 'app/file-uploader.component.html',
    styleUrls: ['app/file-uploader.component.css'],
    // 1. Valores recibidos
    inputs:['activeColor','baseColor','overlayColor']
})
export class FileUploaderComponent {
    // 2. Propiedades
    activeColor: string = 'green';
    baseColor: string = '#ccc';
    overlayColor: string = 'rgba(255,255,255,0.5)';
    
    dragging: boolean = false;
    loaded: boolean = false;
    imageLoaded: boolean = false;
    imageSrc: string = '';
    
    // 3. Funcionalidad Drag & Drop
    handleDragEnter() {
        this.dragging = true;
    }
    
    handleDragLeave() {
        this.dragging = false;
    }
    
    handleDrop(e) {
        e.preventDefault();
        this.dragging = false;
        this.handleInputChange(e);
    }
    
    // 4. Carga de imagen
    handleImageLoad() {
        this.imageLoaded = true;
        this.iconColor = this.overlayColor;
    }

    // 5. Vista Previa
    handleInputChange(e) {
        var file = e.dataTransfer ? e.dataTransfer.files[0] : e.target.files[0];

        var pattern = /image-*/;
        var reader = new FileReader();

        if (!file.type.match(pattern)) {
            alert('invalid format');
            return;
        }

        this.loaded = false;

        reader.onload = this._handleReaderLoaded.bind(this);
        reader.readAsDataURL(file);
    }
    
    _handleReaderLoaded(e) {
        var reader = e.target;
        this.imageSrc = reader.result;
        this.loaded = true;
    }
    
    _setActive() {
        this.borderColor = this.activeColor;
        if (this.imageSrc.length === 0) {
            this.iconColor = this.activeColor;
        }
    }
    
    _setInactive() {
        this.borderColor = this.baseColor;
        if (this.imageSrc.length === 0) {
            this.iconColor = this.baseColor;
        }
    }
    
}

1. Valores recibidos: Mediante el parámetro inputs de la función decorador @Component(), le indicamos a Angular los valores que el componente puede recibir.

2. Propiedades: Declaramos las propiedades del componente que usaremos en los métodos de la aplicación. El primer bloque de propiedades son los valores por defecto en caso de que no se le proporcione ninguno a través de los atributos.

3. Funcionalidad Drag & Drop: Estos métodos responden a los eventos de arrastre sobre el componente y actualizan el valor dragging que usaremos en la plantilla para dar retroalimentación visual al usuario.

4. Carga de imagen: Por defecto mantenemos el elemento de la imagen oculta y al cargarse por completo la imagen, mediante este método actualizamos los valores que nos ayudarán a mostrarla en la plantilla.

5. Vista Previa: Este método responde al cambio del valor en el campo input de tipo file y a través de la clase FileReader de HTML5 leemos el contenido del archivo y lo asignamos al elemento de imagen de HTML.

Los métodos declarados con un _ son para indicar que son de uso interno, el resto de los métodos de la clase son llamados desde la plantilla como veremos posteriormente.

Plantilla

Ahora vamos a crear la plantilla del componente. Crea un archivo con el nombre file-uploader.component.html y coloca el siguiente código:

<label class="uploader" ondragover="return false;"
    [class.loaded]="loaded" 
    [style.outlineColor]="dragging ? activeColor : baseColor"
    (dragenter)="handleDragEnter()"
    (dragleave)="handleDragLeave()"
    (drop)="handleDrop($event)">
    
    <i class="icon icon-upload" 
        [style.color]="dragging 
            ? ((imageSrc.length > 0) ? overlayColor : activeColor)
            : ((imageSrc.length > 0) ? overlayColor : baseColor)"></i>
    
    <img 
        [src]="imageSrc" 
        (load)="handleImageLoad()" 
        [class.loaded]="imageLoaded"/>
    
    <input type="file" name="file" accept="image/*"
        (change)="handleInputChange($event)">
</label>

Muchas cosas suceden en esta plantilla, así que voy a explicar las mas relevantes:

Linea 1: Se habilita el elemento para responder al drag & drop cancelando el comportamiento por defecto (ondragover="return false").

Lineas 4, 5 y 6: Asignamos los métodos que responden a los eventos de drag & drop. En Angular 2 se usa la notación (evento)="función()" que equivale a onEvento="función".

Linea 14: Asignamos al atributo src de la imagen el valor de la variable imageSrc que contendrá la cadena base64 obtenida del FileReader.

Linea 19: Asignamos el método que se encarga de actualizar la vista previa cuando se lee un nuevo archivo.

Los estilos

Crea un archivo con el nombre file-uploader.component.css y coloca el siguiente código:

/* File Uploader Styles  */

.uploader input {
  display: none;
}

.uploader {
  align-items: center;
  background-color: #efefef;
  background-color: rgba(0, 0, 0, 0.02);
  cursor: pointer;
  display: -webkit-flex;
  display: flex;
  height: 300px;
  justify-content: center;
  outline: 3px dashed #ccc;
  outline-offset: 5px;
  position: relative;
  width: 300px;
}

.uploader img,
.uploader .icon {
  pointer-events: none;
}

.uploader,
.uploader .icon {
  transition: all 100ms ease-in;
}

.uploader .icon {
  color: #eee;
  color: rgba(0, 0, 0, 0.2);
  font-size: 5em;
}

.uploader img {
  left: 50%;
  opacity: 0;
  max-height: 100%;
  max-width: 100%;
  position: absolute;
  top: 50%;
  transition: all 300ms ease-in;
  transform: translate(-50%, -50%);
  z-index: -1;
}

.uploader img.loaded {
  opacity: 1;
}

Para efectos de legibilidad estoy omitiendo los prefijos de los navegadores en ciertas propiedades como transform y transition pero puedes consultar el código completo en el demo.

Listo, si abres el archivo index.html en tu explorador debe verse igual al demo colocado al principio del artículo.


Conclusión

Angular 2 tiene muchos conceptos nuevos que, aunque no son complicados, puede ser difícil acostumbrarse al principio. En artículos siguientes abarcaré mas conceptos a través de ejemplos prácticos como el de este artículo, si hay algún caso práctico en particular que te gustaría ver no dudes en dejar tus sugerencias en los comentarios.

En el siguiente y último artículo de esta serie vamos a ver como crear un componente para el input file usando ReactJS.



Publicaciones que pueden interesarte

    Comentarios

    Una comentario en “Como crear un componente de Angular 2 para input file personalizado, con vista previa y drag & drop

    1. Pingback: air jordan site

    Deja un comentario

      tope
    Derechos Reservados, Merida Design 2017
    %d bloggers like this: