Carga de archivos a cloud storage por medio de firebase en angular
Tags: angular, firebase, cloudstorage, gcp
Published: 2020-05-28 18:20:01

## Contenido
 * [Introducción](#chapter-1)
 * [Configurar proyecto de firebase](#chapter-2)
 * [Crear proyecto de Angular](#chapter-3)
 * [Conectar angular con firebase](#chapter-4)

## Introducción <a name="chapter-1"></a>
En este post se muestra como realizar la carga de archivos a cloud storage a través de firebase.

## Configurar proyecto de firebase <a name="chapter-2"></a>
Para configurar el proyecto de firebase se debe realizar los siguientes pasos:
1. Acceder a [firebase console](https://console.firebase.google.com/) y crear un nuevo proyecto.
![Crear proyecto](https://res.cloudinary.com/dzxysoqx3/image/upload/v1590687269/CrearProyecto_jbovkg.png)

2. Habilitar storage en firebase.
Para esto en el menú lateral se encuentra la opción de storage, al ingresar a dicha opción se da click en botón de comenzar y se siguen los pasos.
![Configurar storage](https://res.cloudinary.com/dzxysoqx3/image/upload/v1590688474/confi-storage_wcbvro.png)
Finalizado el proceso la ventana de storage se mostrara de la siguiente manera:
![storage](https://res.cloudinary.com/dzxysoqx3/image/upload/v1590688705/storage-firebase_snlvie.png)
Si se desea se puede ir a cloud storage y verificar la creación de bucket
![Cloud storage](https://res.cloudinary.com/dzxysoqx3/image/upload/v1590688773/storage-test-angular_tfwfcf.png)
Por ultimo se va a la pestaña de Rules y se cambia la regla para que no solicite autenticarse para realizar la carga de los archivos.
La cual queda de la siguiente manera:
![Cambiar regla](https://res.cloudinary.com/dzxysoqx3/image/upload/v1590689146/change-rules_utncoz.png)
3. Agregar app en firebase
En la ventana principal del proyecto se encuentra la opción de agregar app, donde muestra diferentes plataformas a seleccionar en este caso se selecciona Web y se siguen los pasos.
![Agregar app en fireabse](https://res.cloudinary.com/dzxysoqx3/image/upload/v1590689800/crear-app-firebase_oew3ix.png).

## Crear proyecto de Angular <a name="chapter-3"></a>
Si desea puede omitir este paso y clonar el repositorio https://github.com/plinio141/test-storage-angular.
Para crear y configurar el proyecto de Angular utilizar los siguientes comandos:
```
 ng new test-storage-angular
 cd test-storage-angular
 ng add @angular/material
 npm i -s @angular/flex-layout 
```
Posteriormente agregar al app.module lo siguiente
 ```javascript
import {MatButtonModule} from '@angular/material/button';
import { FlexLayoutModule } from '@angular/flex-layout';

@NgModule({
  ...
  imports: [
    ...
    MatButtonModule,
    FlexLayoutModule
  ],
  ...
})
 ```

## Conectar angular con firebase <a name="chapter-4"></a>
### Para esto seguir los siguientes pasos:
Tomar el Json que genero firebase al registrar la aplicación y agregarlo al archivo de environment en angular de la siguiente manera:
```javascript
export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    databaseURL: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    projectId: "XXXXXXXXXXXXXXXXXXXXX",
    storageBucket: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    messagingSenderId: "XXXXXXXXXXXXXXXXXXXXXXXXX",
    appId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  }
};
```
Instalar librería firebase y @angular/fire
```
 npm i --save firebase
 npm i @angular/fire --save
```
Importar AngularFireModule, AngularFireStorageModule y environment en el app.module
```javascript
import { AngularFireModule } from '@angular/fire';
import { AngularFireStorageModule } from '@angular/fire/storage';
import { environment } from '../environments/environment';
```
Agregar a los imports las siguientes lineas:
```javascript
AngularFireModule.initializeApp(environment.firebaseConfig),
AngularFireStorageModule,
```
El app.module quedaría de la siguiente manera:
```javascript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AngularFireModule } from '@angular/fire';
import { AngularFireStorageModule } from '@angular/fire/storage';
import { MatButtonModule } from '@angular/material/button';
import { FlexLayoutModule } from '@angular/flex-layout';

import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    MatButtonModule,
    FlexLayoutModule,
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFireStorageModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```
Ahora se crea una interfaz gráfica simple que contenga un input tipo file y un botón que tendrá el evento de carga del archivo en este caso se hará en el app.component.html
```html
<section fxLayout="column" fxLayoutAlign="space-around center" fxFlex="100" fxLayoutGap="20px">
  <div class="label" fxFlex="30">Archivo</div>
  <div fxFlex="30"><input type="file" name="file"/></div>
  <div fxFlex="30">
    <button mat-flat-button color="primary">Cargar</button>
  </div>
</section>
```
Si desea puede agregar los siguientes estilos:
```scss
section {
  padding: 10px;
}
.label {
  font-size: 30px;
}
```
Ahora en app.component.ts se agrega una propiedad llamada file de tipo File y se crea una función que va capturar el evento de selección de una imagen.
```javascript
  file: File;
```
```javascript
onFileSelect(event) {
    if (event.target.files.length > 0) {
      this.file = event.target.files[0];
    }
  }
```
Se agrega al input file el evento change apuntando a la función anteriormente creada.
```html
<div fxFlex="30"><input type="file" name="file" (change)="onFileSelect($event)"/></div>
```
En el app.component.ts importar el AngularFireStorage y agregar una nueva función que sera la encargada de cargar los archivos.
```javascript
import { AngularFireStorage } from '@angular/fire/storage';
...
 constructor(
    private storage: AngularFireStorage
 ){}
...
uploadFile() {
    const filePath = this.file.name;
    // Crea una referencia de acceso
    const fileRef = this.storage.ref(filePath);
    fileRef.put(this.file);
  }
``` 
Agregar al botón Cargar el evento click para que cargue el archivo.
```html
<button mat-flat-button color="primary" (click)="uploadFile()">Cargar</button>
```  
Con esto ya se carga un archivo a cloud storage por medio de firebase, pero visualmente no se identifica que ya fue cargado el archivo así que se va crear un mensaje de carga completa. Para esto se añade en el html lo siguiente:
```html
<div class="label" fxFlex="30" *ngIf="completed">Archivo cargado</div>
```
Además, en el app.component.ts se agrega una propiedad para mostrar y ocultar el mensaje de carga completa, de igual manera se debe modificar la función de uploadFile para actualizar la propiedad completed cuando se termine de cargar el archivo.
```javascript
uploadFile() {
    this.completed = false;
    const filePath = this.file.name;
    // Crea una referencia de acceso
    const fileRef = this.storage.ref(filePath);
    fileRef.put(this.file).then(() => {
      this.completed = true;
    });
  }
```
Por ultimo se puede añadir una barra de carga. Para esto se va hacer uso del componente mat-progress-bar de angular material para lo cual se debe importar en el app.module.

```javascript
...
import { MatProgressBarModule } from '@angular/material/progress-bar';
...
imports: [
    ...
    MatProgressBarModule,
    ...
  ],
...
```
Para usar el componente se añade al html de la siguiente manera:
```html
<mat-progress-bar *ngIf="!completed" mode="determinate" value="uploadPercent | async"></mat-progress-bar>
```
En el app.component se añade la propiedad uploadPercent y se actualiza la función uploadFile para esto se debe importar finalize y Observable para un funcionamiento correcto.
````javascript
import { finalize } from 'rxjs/operators';
import { Observable } from 'rxjs';
...
uploadFile() {
    this.completed = false;
    const filePath = this.file.name;
    const task = this.storage.upload(filePath, this.file);

    this.uploadPercent = task.percentageChanges();

    task.snapshotChanges().pipe(
      finalize(() => {
        this.completed = true;
      })
    )
    .subscribe();
  }
```
Los archivos quedan de la siguiente manera:
app.module.ts
```javascript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AngularFireModule } from '@angular/fire';
import { AngularFireStorageModule } from '@angular/fire/storage';
import { MatButtonModule } from '@angular/material/button';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatProgressBarModule } from '@angular/material/progress-bar';

import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { environment } from '../environments/environment';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    MatButtonModule,
    MatProgressBarModule,
    FlexLayoutModule,
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFireStorageModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```
app.component.ts
```javascript
import { Component } from '@angular/core';
import { AngularFireStorage } from '@angular/fire/storage';
import { finalize } from 'rxjs/operators';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  file: File;
  completed = false;
  uploadPercent: Observable<number>;

  constructor(
    private storage: AngularFireStorage
  ){}

  uploadFile() {
    this.completed = false;
    const filePath = this.file.name;
    const task = this.storage.upload(filePath, this.file);

    this.uploadPercent = task.percentageChanges();

    task.snapshotChanges().pipe(
      finalize(() => {
        this.completed = true;
      })
    )
    .subscribe();
  }

  onFileSelect(event) {
    if (event.target.files.length > 0) {
      this.file = event.target.files[0];
    }
  }

}
```
app.component.html
```html
<section fxLayout="column" fxLayoutAlign="space-around center" fxFlex="100" fxLayoutGap="20px">
  <div class="label" fxFlex="30">Archivo</div>
  <div fxFlex="30"><input type="file" name="file" (change)="onFileSelect($event)"/></div>
  <div fxFlex="30">
    <button mat-flat-button color="primary" (click)="uploadFile()">Cargar</button>
  </div>
  <mat-progress-bar mode="determinate" [value]="uploadPercent | async"></mat-progress-bar>
  <div class="label" fxFlex="30" *ngIf="completed">Archivo cargado</div>
</section>
```

Este código se encuentra en https://github.com/plinio141/test-storage-angular