Tutorial de Angular 6: Nuevas características con nueva potencia
¡Angular 6 ha salido! Los cambios más destacados están en su CLI y en cómo se inyectan los servicios. Si quieres escribir tu primera aplicación de Angular 6 -o de Angular/Firebase- en este tutorial repasaremos los pasos básicos de la configuración inicial y crearemos una pequeña aplicación de diario.
Angular 6: lo básico
Si nunca has usado Angular, déjame darte una breve descripción del mismo y de su funcionamiento.
Angular es un framework de JavaScript diseñado para soportar la construcción de aplicaciones de una sola página (SPAs) tanto para escritorio como para móviles.
El framework incluye un conjunto completo de directivas y módulos que permiten implementar fácilmente algunos de los escenarios más comunes para una aplicación web, como la navegación, la autorización, los formularios y los informes. También viene con todos los paquetes necesarios para añadir pruebas utilizando el marco Jasmine y ejecutarlas con los ejecutores de pruebas Karma o Protractor.
La arquitectura de Angular se basa en componentes, plantillas, directivas y servicios. Proporciona un mecanismo de inyección de dependencia incorporado para sus servicios, así como la vinculación de datos de dos vías para conectar sus vistas con su código de componentes.
Angular utiliza TypeScript, un superconjunto tipado de JS, y hará algunas cosas más fáciles, especialmente si usted viene de un fondo de lenguaje tipado.
Angular 6: Nuevas características
Un breve resumen de las nuevas características de Angular 6:
- Una política de sincronización de los principales números de versión para los paquetes del framework (
@angular/core
,@angular/common
,@angular/compiler
, etc.), CLI, Material y CDK. Esto ayudará a que la compatibilidad cruzada sea más clara en el futuro: Usted puede decir de un vistazo rápido en el número de versión si los paquetes clave son compatibles entre sí. - Nuevo
ng
comandos CLI:-
ng update
para actualizar las versiones de los paquetes de forma inteligente, actualizando las versiones de las dependencias y manteniéndolas sincronizadas. (Por ejemplo, al ejecutarng update @angular/core
se actualizarán todos los frameworks así como RxJS.) También ejecutará los esquemas si el paquete los incluye. (Si una versión más nueva incluye cambios de ruptura que requieren cambios en el código, el esquema actualizará su código por usted.) -
ng add
para añadir nuevos paquetes (y ejecutar scripts, si es el caso)
-
- Los servicios ahora hacen referencia a los módulos que los proporcionarán, en lugar de que los módulos hagan referencia a los servicios, como solían hacerlo.
Como ejemplo de lo que significa este último cambio, donde tu código se veía como:
@NgModule({ // ... providers: })
…con este cambio en particular en Angular 6, se verá como:
@Injectabe({ providedIn: 'root',})
Estos se llaman proveedores tree-shakeable y permiten que el compilador elimine los servicios no referenciados resultando en paquetes de menor tamaño.
Angular 6 CLI
La interfaz de línea de comandos ng
es una pieza muy importante de Angular y te permite moverte más rápido a la hora de codificar tu app.
Con la CLI puedes andamiar la configuración inicial de tu app muy fácilmente, generar nuevos componentes, directivas, etc, y construir y ejecutar tu app en tu entorno local.
Crear un proyecto de Angular 6
Bien, basta de hablar. Vamos a ensuciarnos las manos y empezar a codificar.
Para empezar, necesitarás Node.js y npm instalados en tu máquina.
Ahora, vamos a seguir adelante e instalar la CLI:
npm install -g @angular/cli
Esto instalará el comando ng
CLI globalmente, debido al interruptor -g
.
Una vez que tenemos eso, podemos obtener el andamiaje inicial para nuestra aplicación con ng new
:
ng new my-memories --style=scss
Esto creará una carpeta my-memories
, creará todos los archivos necesarios para tener su configuración inicial lista para comenzar, e instalará todos los paquetes necesarios. El interruptor --style=scss
es opcional y configurará el compilador para compilar los archivos SCSS a CSS, que necesitaremos más tarde.
Una vez completada la instalación, puedes cd my-memories
y ejecutar ng serve
. Esto iniciará el proceso de construcción y un servidor web local que sirve su aplicación en http://localhost:4200
.
Lo que ocurre entre bastidores es que el CLI transpila todos los .ts
(archivos TypeScript) a vanilla JS, recoge todas las dependencias necesarias de la carpeta de paquetes node_modules
, y emite el resultado en un conjunto de archivos que se sirven a través de un servidor web local que se ejecuta en el puerto 4200.
Archivos del proyecto
Si no estás familiarizado con la estructura de carpetas del proyecto de Angular, lo más importante que debes saber es que todo el código relacionado con la aplicación va dentro de la carpeta src
. Normalmente crearás todos tus módulos y directivas en esa carpeta siguiendo la arquitectura de tu app (por ejemplo, usuario, carrito, producto.)
Configuración inicial
Bien, hasta aquí tenemos la configuración inicial de nuestra app. Vamos a empezar a hacer algunos cambios.
Antes de empezar, vamos a indagar un poco en la carpeta src
. La página inicial es index.html
:
<!doctype html><html lang="en"><head> <meta charset="utf-8"> <title>MyMemories</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"></head><body> <app-root></app-root></body></html>
Aquí vemos algo de HTML básico y la etiqueta <app-root>
. Esto es un componente de Angular, y donde Angular 6 inserta el código de nuestro componente.
Encontraremos el archivo app/app.component.ts
, que tiene el selector app-root
para que coincida con lo que hay en el archivo index.html
.
El componente es una clase TypeScript decorada, y en este caso, contiene la propiedad title
. El decorador @Component
le dice a Angular que incluya el comportamiento del componente en la clase. Además del selector, especifica qué archivo HTML renderizar y qué hojas de estilo utilizar.
import { Component } from '@angular/core';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: })export class AppComponent { title = 'app';}
Si nos fijamos en app.component.html
veremos el binding de interpolación {{title}}
. Aquí es donde ocurre todo el binding mágico, y Angular renderizará el valor de la propiedad title de la clase y lo actualizará cada vez que cambie.
<!--The content below is only a placeholder and can be replaced.--><div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="></div><h2>Here are some links to help you start: </h2><ul> <li> <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2> </li> <li> <h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2> </li> <li> <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2> </li></ul>
Vamos a actualizar el title
de la clase a 'My Memories!'
.
...export class AppComponent { title = 'My Memories!';}...
Veremos como el compilador procesa nuestro cambio y el navegador se refresca para mostrar nuestro título actualizado.
Esto significa que el ng serve
de Angular 6 vigila los cambios de nuestro archivo y renderiza cada vez que se introduce un cambio en cualquier archivo.
Para hacer más amigable la codificación y evitar el refresco completo de la página cada vez que hacemos cambios, podemos aprovechar el Hot Module Replacement (HMR) de webpack, que sólo actualiza el trozo de JS/CSS que fue cambiado en lugar de producir un refresco completo para mostrar sus cambios.
Configurar HMR
Primero, necesitamos configurar el entorno.
Crea un archivo src/environments/environment.hmr.ts
con el siguiente contenido:
export const environment = { production: false, hmr: true};
Actualiza src/environments/environment.prod.ts
y añade la bandera hmr: false
al entorno:
export const environment = { production: true, hmr: false};
Entonces actualiza src/environments/environment.ts
y añade la bandera hmr: false
al entorno allí también:
export const environment = { production: false, hmr: false};
A continuación, en el archivo angular.json
, actualiza esta parte:
"projects": { "my-memories": { // ... "architect": { "build": { // ... "configurations": { "hmr":{ "fileReplacements": }, // ...
Y bajo projects
→ my-memories
→ architect
→ serve
→ configurations
:
"projects": { "my-memories": { "architect": { // ... "serve": { // ... "configurations": { "hmr": { "browserTarget": "my-memories:build:hmr" }, // ...
Ahora actualiza tsconfig.app.json
para incluir el necesario types
(bueno, tipo) añadiendo esto bajo compilerOptions
:
"compilerOptions": { // ... "types":
A continuación, instalaremos el módulo @angularclass/hmr
como dependencia de desarrollo:
npm install --save-dev @angularclass/hmr
Luego configúralo creando un archivo src/hmr.ts
:
import { NgModuleRef, ApplicationRef } from '@angular/core';import { createNewHosts } from '@angularclass/hmr';export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => { let ngModule: NgModuleRef<any>; module.hot.accept(); bootstrap().then(mod => ngModule = mod); module.hot.dispose(() => { const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef); const elements = appRef.components.map(c => c.location.nativeElement); const makeVisible = createNewHosts(elements); ngModule.destroy(); makeVisible(); });};
A continuación, actualizamos src/main.ts
para utilizar la función anterior:
import { enableProdMode } from '@angular/core';import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';import { AppModule } from './app/app.module';import { environment } from './environments/environment';import { hmrBootstrap } from './hmr';if (environment.production) { enableProdMode();}const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);if (environment.hmr) { if (module) { hmrBootstrap(module, bootstrap); } else { console.error('HMR is not enabled for webpack-dev-server!'); console.log('Are you using the --hmr flag for ng serve?'); }} else { bootstrap().catch(err => console.log(err));}
Lo que estamos haciendo aquí es hacer que bootstrap llame a una función anónima, y a continuación preguntar si la bandera environment.hmr
es verdadera. Si lo es, llamamos a la función previamente definida de hmr.ts
que habilitó el reemplazo de módulos en caliente; en caso contrario, hacemos bootstrap como antes.
Ahora, cuando ejecutemos ng serve --hmr --configuration=hmr
, estaremos invocando la configuración hmr
, y cuando hagamos cambios en los archivos, obtendremos las actualizaciones sin un refresco completo. El primer --hmr
es para webpack, y --configuration=hmr
es para que Angular utilice el entorno hmr
.
Aplicación web progresiva (PWA)
Para añadir el soporte de Angular 6 PWA y habilitar la carga offline para la app, podemos hacer uso de uno de los nuevos comandos del CLI, ng add
:
ng add @angular/
Nótese que añado la versión, ya que la última versión cuando estaba escribiendo este tutorial arrojaba un error. (Puedes probar sin ella y comprobar si te funciona usando simplemente ng add @angular/pwa
.)
Bien, pues después de ejecutar el comando veremos muchos cambios en nuestro proyecto. Los cambios más importantes son que agregó:
- Una referencia a
manifest.json
en el archivo de activosangular.json
, para que se incluya en la salida de la compilación, así como"serviceWorker": true
en las compilaciones de producción - El archivo
ngsw-config.json
con la configuración inicial para almacenar en caché todos los archivos necesarios para que la app se ejecute - Una meta etiqueta
manifest.json
en el archivoindex.html
- El propio archivo
manifest.json
, con una configuración básica para la app - La carga del service worker en el módulo de la app
ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production })
(ten en cuenta que el service worker sólo estará habilitado en entornos de producción)
Así, esto significa ahora que cuando el usuario acceda por primera vez a la URL, se descargarán los archivos. Después, si el usuario intenta acceder a la URL sin servicio de red, la app seguirá funcionando tirando de esos archivos en caché.
Añadir la librería Material Angular 6 UI
Hasta aquí tenemos la configuración inicial, y estamos listos para empezar a construir nuestra app. Para hacer uso de los componentes ya construidos, podemos utilizar la versión Angular 6 de Material.
Para instalar el paquete material
en nuestra app, volveremos a hacer uso de ng add
:
ng add @angular/material
Después de ejecutar eso, veremos algunos paquetes nuevos añadidos y alguna configuración básica de estilo:
-
index.html
incluye la fuente Roboto y los iconos Material -
BrowserAnimationsModule
se añade a nuestroAppModule
-
angular.json
tiene el índigo-tema rosa ya incluido para nosotros
Tendrá que reiniciar ng serve
para recoger el tema, o puedes elegir otro tema preconstruido.
Diseño básico
Para tener el diseño inicial de sidenav, usaremos los esquemas que vienen con Material. Pero no pasa nada si quieres utilizar un diseño diferente.
(En pocas palabras, los esquemas te permiten aplicar transformaciones a un proyecto: Puede crear, modificar o eliminar archivos según sea necesario. En este caso, andamiaje un diseño sidenav para nuestra aplicación.)
ng generate @angular/material:material-nav --name=my-nav
Esto creará un componente sidenav con la mínima configuración listo para empezar. ¿No es genial?
También ha incluido todos los módulos necesarios en nuestro app.module.ts
.
Dado que estamos usando SCSS, tenemos que renombrar el archivo my-nav.component.css
a my-nav.component.scss
, y en my-nav.component.ts
actualizar la referencia correspondiente styleUrls
para usar el nuevo nombre.
Ahora para hacer uso del nuevo componente, vamos a app.component.html
y eliminamos todo el código inicial, dejando sólo:
<app-my-nav></app-my-nav>
Cuando volvamos al navegador, esto es lo que veremos:
Actualicemos los enlaces para tener sólo las dos opciones que queremos.
Primero, vamos a crear dos nuevos componentes:
ng g c AddMemoryng generate @angular/material:material-table --name=view-memories
(El segundo es un esquema de Material utilizado para crear una tabla.)
A continuación, en my-nav
vamos a actualizar para configurar los enlaces e incluir el <router-outlet>
para mostrar nuestros componentes de contenido:
<mat-sidenav-container class="sidenav-container"> <mat-sidenav #drawer class="sidenav" fixedInViewport="true" ="(isHandset$ | async) ? 'dialog' : 'navigation'" ="(isHandset$ | async) ? 'over' : 'side'" ="!(isHandset$ | async)"> <mat-toolbar color="primary">Menu</mat-toolbar> <mat-nav-list> <a mat-list-item ="">Add Memory</a> <a mat-list-item ="">View My Memories</a> </mat-nav-list> </mat-sidenav> <mat-sidenav-content> <mat-toolbar color="primary"> <button type="button" aria-label="Toggle sidenav" mat-icon-button (click)="drawer.toggle()" *ngIf="isHandset$ | async"> <mat-icon aria-label="Side nav toggle icon">menu</mat-icon> </button> <span>my-memories</span> </mat-toolbar> <router-outlet></router-outlet> </mat-sidenav-content></mat-sidenav-container>
Además, en app.component.html
tenemos que actualizarlo para que sólo tenga el <router-outlet>
principal (es decir, eliminar <my-nav>
):
<router-outlet></router-outlet>
A continuación, en el AppModule
incluiremos las rutas:
import { RouterModule, Routes } from '@angular/router';// ...imports: }, ]),]
Nota que estamos poniendo MyNavComponent
como padre y los dos componentes que creamos como hijos. Esto se debe a que incluimos el <router-outlet>
en MyNavComponent
, y cada vez que pulsemos una de esas dos rutas, renderizaremos el componente hijo donde se colocó el <router-outlet>
.
Después de esto, cuando sirvamos la app deberíamos ver:
Construir la app (Un diario de recuerdos)
Bien, ahora vamos a crear el formulario para guardar nuevos recuerdos en nuestro diario.
Necesitaremos importar algunos módulos de material y el módulo de formularios a nuestro app.module.ts
:
import { FormsModule } from '@angular/forms';import { MatCardModule, MatFormFieldModule, MatInputModule, MatDatepickerModule, MatNativeDateModule } from '@angular/material';// ...Imports:
Y luego en add-memory.component.html
, añadiremos el formulario:
<form #form="ngForm" (ngSubmit)="onSubmit()"> <mat-card class="memory-card"> <mat-card-title> Add a memory </mat-card-title> <mat-card-content> <mat-form-field> <input disabled matInput placeholder="Select the date..." ="memory.date" name="date" ="date"> <mat-datepicker-toggle matSuffix ="date"></mat-datepicker-toggle> <mat-datepicker disabled="false" #date></mat-datepicker> </mat-form-field> <mat-form-field> <textarea placeholder="Enter your memory..." rows="3" maxlength="300" matInput ="memory.text" name="memory"></textarea> </mat-form-field> </mat-card-content> <mat-card-actions> <button mat-button type="submit">Save me!</button> </mat-card-actions> </mat-card></form><pre> {{ memory | json }} </pre>
Aquí estamos usando un mat-card
y añadiendo dos campos, un date
y un textarea
.
Nota que estamos usando . Esta directiva de Angular vinculará la expresión
memory.date
y la propiedad memory
de la clase entre sí, como veremos más adelante. ( es azúcar sintáctico-un atajo para realizar una vinculación de datos bidireccional de la clase a la vista y de la vista a la clase. Esto significa que cuando introduces texto en la vista,
memory.date
reflejará esos cambios en la instancia de la clase, y si haces cambios en memory.date
en la instancia de la clase, la vista reflejará los cambios.)
En el add-memory.component.ts
, el código se verá así:
import { Component, OnInit } from '@angular/core';@Component({ selector: 'app-add-memory', templateUrl: './add-memory.component.html', styleUrls: })export class AddMemoryComponent implements OnInit { memory: any = {}; constructor() { } ngOnInit() { } onSubmit() { console.log(this.memory); }}
Aquí, estamos inicializando la propiedad memory
vinculada a través de ngModel
. Cuando el componente AddMemoryComponent
se instancie, memory
será un objeto vacío. Entonces, cuando la directiva ngModel
se ejecute, podrá asignar el valor de entrada a memory.date
y memory.text
. Si no hiciéramos esto, obtendríamos un error de Cannot set property 'date/text' of undefined
.
Mientras tanto, add-memory.component.scss
necesita tener:
.memory-card { min-width: 150px; max-width: 400px; width: 100%; margin: auto;}.mat-form-field { width: 100%;}
Como tenemos <pre> {{ memory | json }} </pre>
podemos ver el estado actual de memory
en la vista. Si vamos al navegador, este es el resultado hasta ahora:
En la vista, vinculamos el formulario a través de (ngSubmit)="onSubmit()"
a la función onSubmit
de la clase.
onSubmit() { console.log(this.memory); }
Así, al pulsar el botón «¡Guárdame!», obtendremos la representación interna de la entrada del usuario enviada al log de la consola:
Tutorial de Angular 6: Conectando con Firebase
Lo que haremos a continuación es conectar nuestro proyecto a Firebase para guardar nuestras memorias.
Primero, iremos a la consola de Firebase y crearemos un proyecto allí.
En segundo lugar, instalaremos los paquetes firebase
y angularfire2
:
npm install firebase angularfire2 --save
Y luego en cada uno de estos tres archivos:
/src/environments/environment.ts
/src/environments/environment.hmr.ts
/src/environments/environment.prod.ts
… añadiremos nuestra configuración Firebase:
export const environment = {// ... firebase: { apiKey: '<your-key>', authDomain: '<your-project-authdomain>', databaseURL: '<your-database-URL>', projectId: '<your-project-id>', storageBucket: '<your-storage-bucket>', messagingSenderId: '<your-messaging-sender-id>' }};
Puedes obtener los detalles de configuración necesarios para los archivos anteriores haciendo clic en «Add Firebase to your web app» en la página de resumen del proyecto.
Después, incluiremos los módulos de Firebase en nuestro app.module.ts
:
import { AngularFireModule } from 'angularfire2';import { AngularFireDatabaseModule } from 'angularfire2/database';import { environment } from '../environments/environment';// ...Imports:
Y en add-memory.component.ts
, inyectamos la base de datos en el constructor y guardamos los valores del formulario en la base de datos. Cuando la promesa push de Firebase tiene éxito, registramos el éxito en la consola y reiniciamos el modelo:
import { AngularFireDatabase } from 'angularfire2/database';// ...constructor(private db: AngularFireDatabase) { }// ... onSubmit() { this.memory.date = new Date(this.memory.date).valueOf(); this.db.list('memories').push(this.memory) .then(_ => { this.memory = {} console.log('success') }) }
Tendrás que permitir el acceso público en las reglas de la base de datos, para que los usuarios anónimos puedan leer y escribir en ella. Ten en cuenta que con esta configuración, cualquier usuario podría leer/cambiar/borrar los datos de tu aplicación. Asegúrese de configurar sus reglas en consecuencia antes de ir a la producción.
También, para recoger los cambios de entorno que tendrá que reiniciar el proceso ng serve
.
Ahora, cuando volvemos al navegador y haga clic en nuestro botón de guardar, veremos la memoria se añadió a la base de datos:
Vamos a ver cómo podemos recuperar nuestras memorias y mostrarlas en la tabla Material.
En su día creamos la tabla utilizando ng generate @angular/material:material-table --name=view-memories', we automatically got a file
view-memories/view-memories-datasource.ts`. Este archivo contiene datos falsos, así que tendremos que cambiarlo para empezar a tirar de Firebase.
En view-memories-datasource.ts
, eliminaremos el EXAMPLE_DATA
y pondremos un array vacío:
export class ViewMemoriesDataSource extends DataSource<ViewMemoriesItem> { data: ViewMemoriesItem = ;// ...
Y en getSortedData
actualizaremos los nombres de los campos:
private getSortedData(data: ViewMemoriesItem) { if (!this.sort.active || this.sort.direction === '') { return data; } return data.sort((a, b) => { const isAsc = this.sort.direction === 'asc'; switch (this.sort.active) { case 'text': return compare(a.name, b.name, isAsc); case 'date': return compare(+a.id, +b.id, isAsc); default: return 0; } });}
En view-memories.component.html
actualizaremos los nombres de las columnas a date
y text
de nuestro modelo de memoria. Obsérvese que como hemos guardado la fecha en formato de milisegundos, aquí estamos utilizando una tubería de fecha para transformar el valor para su visualización en un formato de fecha más amigable. Por último, eliminamos el ="dataSource.data.length"
del paginador, ya que cargaremos los datos de forma asíncrona desde Firebase:
<div class="mat-elevation-z8"> <table mat-table #table ="dataSource" matSort aria-label="Elements"> <!-- Id Column --> <ng-container matColumnDef="date"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Date</th> <td mat-cell *matCellDef="let row">{{row.date | date:'short'}}</td> </ng-container> <!-- Name Column --> <ng-container matColumnDef="text"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Text</th> <td mat-cell *matCellDef="let row">{{row.text}}</td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table> <mat-paginator #paginator ="0" ="50" =""> </mat-paginator></div>
Cambiamos el view-memories.component.css
por view-memories.component.scss
y establecemos el estilo de la tabla:
table{ width: 100%;}
En view-memories.component.ts
, cambiaremos el styleUrls
para reflejar el cambio de nombre anterior a ./view-memories.component.scss
. También actualizaremos el array displayedColumns
para que sea y configuraremos el datasource de la tabla para obtener los datos de Firebase.
Lo que ocurre aquí es que nos estamos suscribiendo a la lista de memorias y cuando recibimos los datos instanciamos el ViewMemoriesDataSource
y establecemos su propiedad data con los datos de Firebase.
this.subscription = this.db.list<ViewMemoriesItem>('memories').valueChanges().subscribe(d=>{ console.log('data streaming'); this.dataSource = new ViewMemoriesDataSource(this.paginator, this.sort); this.dataSource.data = d;});
Firebase devuelve un array Observable de estilo ReactiveX.
Nota que estamos casteando this.db.list<ViewMemoriesItem>('memories')
los valores extraídos de la ruta 'memories'
a ViewMemoriesItem
. De esto se encarga la librería angularfire2
.
También hemos incluido la llamada unsubscribe
dentro del hook onDestroy
del ciclo de vida de los componentes de Angular.
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';import { MatPaginator, MatSort } from '@angular/material';import { ViewMemoriesDataSource, ViewMemoriesItem } from './view-memories-datasource';import { AngularFireDatabase } from 'angularfire2/database';import { Subscription } from 'rxjs';import { map, first } from 'rxjs/operators';@Component({ selector: 'app-view-memories', templateUrl: './view-memories.component.html', styleUrls: })export class ViewMemoriesComponent implements OnInit, OnDestroy{ @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; dataSource: ViewMemoriesDataSource; /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ displayedColumns = ; subscription: Subscription; constructor(private db: AngularFireDatabase) { } ngOnInit() { this.subscription = this.db.list<ViewMemoriesItem>('memories').valueChanges().subscribe(d=>{ console.log('data streaming'); this.dataSource = new ViewMemoriesDataSource(this.paginator, this.sort); this.dataSource.data = d; }); } ngOnDestroy(): void { this.subscription.unsubscribe(); }}
Desplegando a Firebase Hosting
Ahora, para que nuestra app esté viva, vamos a desplegarla en Firebase Hosting. Para ello, instalaremos la CLI de Firebase, que hace que el comando firebase
esté disponible:
npm install -g firebase-tools
Ahora podemos usar la CLI de Firebase para iniciar sesión:
firebase login
Esto te pedirá que selecciones tu cuenta de Google.
A continuación, inicializaremos el proyecto y configuraremos el Hosting de Firebase:
firebase init
Sólo seleccionaremos la opción de Hosting.
A continuación, cuando nos pidan la ruta, la pondremos en dist/my-memories
. Cuando nos pregunten si queremos configurarlo como una aplicación de una sola página (es decir, reescribir todas las URLs a /index.html
), responderemos «sí».»
Por último, le daremos a «El archivo dist/mi-memoria/index.html ya existe. ¿Sobreescribir?» Aquí diremos «no».»
Esto creará los archivos de configuración de Firebase .firebaserc
y firebase.json
con la configuración proporcionada.
El último paso es ejecutar:
ng build --prodfirebase deploy
Y con eso, habremos publicado la app en la Firebase, que nos proporciona una URL a la que podemos navegar, como https://my-memories-b4c52.firebaseapp.com/view-memories, donde podéis ver mi propia demo publicada.
¡Vaya, ya habéis pasado por el tutorial! Espero que lo hayas disfrutado. También puedes ver el código completo en GitHub.
Un paso a la vez
Angular es un framework muy potente para construir aplicaciones web. Lleva mucho tiempo en el mercado y ha demostrado su eficacia tanto en aplicaciones pequeñas y sencillas como en las grandes y complejas.
En el futuro, Angular planea seguir mejorando y siguiendo nuevos paradigmas web como los componentes web (Angular Elements). Si estás interesado en construir aplicaciones híbridas, puedes echar un vistazo a Ionic, que utiliza Angular como su marco subyacente.
Este tutorial cubrió pasos muy básicos para comenzar a usar Angular, Material y Firebase. Pero debes tener en cuenta que para aplicaciones del mundo real, necesitarás añadir validación, y para que tu aplicación sea más fácil de mantener y escalar, probablemente quieras seguir las mejores prácticas como el uso de Servicios, componentes reutilizables, etc. Eso tendrá que ser el tema de otro artículo-¡Espero que este haya sido suficiente para abrir tu apetito por el desarrollo de Angular!
Leave a Reply