Finales del 2023, qué comprar para empezar a usar arduino. Porqué no usar arduino, al menos su controlador, si su entorno y una guía de compra para que programes sin necesidad de entender la electrónica, ya que realmente hay que saber muy pocos conceptos. En este post te cuento todo lo que necesitas, con mi experiencia desde 2018. A mis espaldas cuento con una lista larga de errores cometidos, te escribo para tú no los cometas y consigas que tus proyectos se hagan realidad y no se queden en tu cabeza, o en la mesa cogiendo polvo.
Arduino es una plataforma de hardware y software de código abierto que facilita la creación de prototipos electrónicos interactivos. Desde su introducción, ha ganado popularidad gracias a su simplicidad y accesibilidad para principiantes.
Arduino ha revolucionado el mundo de la electrónica y el desarrollo DIY (Do It Yourself) desde su introducción en 2005. Con su enfoque centrado en la facilidad de uso, ha logrado democratizar el acceso a la creación de proyectos electrónicos, permitiendo a principiantes y aficionados de todas las edades y habilidades sumergirse en este fascinante universo.
Arduino ha creado un ecosistema que va más allá de sus propias placas y microcontroladores. Su plataforma ha establecido un estándar en la comunidad DIY, y su influencia es evidente en la cantidad de hardware y software que se ha desarrollado inspirado en o para ser compatible con Arduino.
Ha sentado un precedente en el mundo de la electrónica, y su plataforma ha sido adaptada y adoptada por varios otros microcontroladores para aprovechar su entorno y las vastas librerías disponibles. A continuación, te presento una lista de algunos de los microcontroladores más populares que pueden ser programados con el entorno Arduino, ordenados desde los más populares:
Aquí te presento una lista de beneficios al elegir el ESP32 frente a un Arduino tradicional:
Si bien el ESP32 ofrece muchos beneficios, es esencial considerar las necesidades específicas de tu proyecto. En algunos casos, un Arduino tradicional podría ser más que suficiente, y en otros, el ESP32 podría ser la elección clara debido a sus capacidades avanzadas.
El ESP32 es una plataforma versátil que admite varios lenguajes de programación. Aquí te detallo algunos de los lenguajes más populares y los IDEs o herramientas asociadas que puedes usar para desarrollar con ellos:
Y es aquí cuando te dijo, ¿Qué lenguaje es el que más te gusta? . Para los amantes de python decirte que no todo se puede hacer con él, no dispone de todas las librerías para su uso, por ejemplo el manejo de la cámara para hacer fotos y videos. Pero para una gran cantidad de proyectos es ideal.
Mi consejo es Usar Platformio (plugin de vscode), con c++ (arduino).
Usar un ESP32 con Arduino en PlatformIO es una excelente opción para aprovechar las capacidades de este microcontrolador con una interfaz amigable y poderosa. Aquí te guiaré paso a paso en la configuración y primeros pasos para trabajar con un ESP32 en PlatformIO:
Instalación de Visual Studio Code (VSCode):
Instalación de PlatformIO en VSCode:
Ctrl+Shift+X
).Crear un nuevo proyecto:
Estructura del Proyecto:
src
: Aquí es donde colocarás tu código principal (por defecto, tendrás un archivo main.cpp
).lib
: Es donde puedes colocar bibliotecas adicionales o tus propias bibliotecas.platformio.ini
: Es el archivo de configuración de tu proyecto. Aquí puedes especificar versiones de frameworks, bibliotecas, flags de compilación y más.Escribir y Cargar un Programa:
src/main.cpp
.#include <Arduino.h>
void setup() {
pinMode(2, OUTPUT); // El pin 2 suele ser el LED integrado en la mayoría de los módulos ESP32. otros no tienen, conecta uno junto con una resistencia de 220ohm
}
void loop() {
digitalWrite(2, HIGH); // Enciende el LED.
delay(1000); // Espera un segundo.
digitalWrite(2, LOW); // Apaga el LED.
delay(1000);
}
Monitor Serial:
Bibliotecas y Dependencias:
Es un resumen breve, debes aprender a usar el archivo platformio.ini, y cosas que en un simple post no puedo explicarte, pero hay muchos videos de youtube.
La función delay()
es una función proporcionada por el framework de Arduino para pausar la ejecución del programa durante un número especificado de milisegundos. Por ejemplo, delay(1000) pausará la ejecución durante 1 segundo. 1 segundo todo el programa, absolutamente todo. ¿Eso es lo que quieres?
Limitaciones de delay()
:
Solución con millis()
: La función millis() devuelve el número de milisegundos desde que el programa Arduino comenzó a ejecutarse. Esto puede usarse para crear intervalos de tiempo no bloqueantes.
Ejemplo práctico con millis(): Supongamos que quieres hacer parpadear un LED cada segundo, pero al mismo tiempo, deseas verificar constantemente un botón para encender o apagar otro LED.
Sin millis(), podrías tener tentación de usar delay() para el parpadeo, pero eso bloquearía la capacidad de detectar el botón. Con millis(), puedes hacer ambas cosas simultáneamente:
const int ledPin = 13; // Pin del LED parpadeante.
const int buttonPin = 2; // Pin del botón.
const int otherLedPin = 12; // Pin del otro LED.
unsigned long previousMillis = 0;
const long interval = 1000; // Intervalo para parpadear (1 segundo).
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT);
pinMode(otherLedPin, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
// Parpadeo del LED sin bloquear:
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (digitalRead(ledPin) == LOW) {
digitalWrite(ledPin, HIGH); // Enciende el LED.
} else {
digitalWrite(ledPin, LOW); // Apaga el LED.
}
}
// Comprobación del botón:
if (digitalRead(buttonPin) == HIGH) {
digitalWrite(otherLedPin, HIGH); // Enciende el otro LED.
} else {
digitalWrite(otherLedPin, LOW); // Apaga el otro LED.
}
}
A la hora de ponerte a buscar que comprar, te inquieta ver la cantidad de dispositivos, con sus respectivas configuraciones, esp32 si, pero con pantalla, sin pantalla, con gps, con sonido, con batería, con usb tipo c o no, …
Los kits como Freenove, TTGO, LilyGO y M5Stack, ofrecen beneficios adicionales, como pantallas integradas, altavoces, fácil acceso a pines para sensores y protecciones adicionales contra cortocircuitos. Estos componentes adicionales pueden hacer que la experiencia de desarrollo sea más intuitiva y segura, especialmente para aquellos que no tienen mucha experiencia en electrónica. Sin embargo, es importante investigar las especificaciones y características de cada kit para asegurarte de que se ajuste a tus necesidades.
Si estás indeciso, y no te importa mucho el dinero apuesta por M5stack. No solo son sus dispositivos. Han creado una linea de librerías, todas comentadas en GitHub que te llevarán la electrónica a otro mundo. Su precio es elevado pero su calidad es suprema.
El resto tiene precios razonables, que pueden triplicar el precio de un esp32, pero que aún así merece la pena, ya que cuentan casi siempre de más memoria, y mejor calidad.
Ten en cuenta que muchas tiendas de Aliexpress si compras esp32 normales te los pueden vender sin soldar, si no cuentas con soldador te puede generar un impedimento extra.
Ten cuidado que ponga esp32 y no esp8266, ya que esta es una vs anterior, y no cuenta con todo lo que tiene esp32, aunque en muchas cosas es compatible, no todo. Si tiras por lo barato, todos los caminos te llebarán a él.
Todos los que he mencionado cuentan con cuenta GitHub, con una lista muy grande de ejemplos de ejecutar directamente.
Evita comprar kits de arduino, o raspberry, estos no son 100% compatibles, ya que funcionana a 5v, y esp32 solo su conexión, el resto de los pines para conectar sensores funcionan a 3.3v. Ten por seguro que a tu esp32 le saldría humo y a tirar.
No te lances a comprar como loco todo lo que ves. Compra lo justo, junto con unos sensores que te vendan esas empresas, para usar sus ejemplos.
Si bien no puedes ejecutar Angular, React o Vue directamente en un ESP32, puedes construir una aplicación web con cualquiera de estos frameworks que se comunique con el ESP32.
Por ejemplo, podrías tener una aplicación web en Angular que utiliza WebSockets para comunicarse con un servidor que, a su vez, se comunica con el ESP32. O podrías tener una aplicación que se conecta directamente al ESP32 a través de Bluetooth utilizando alguna API de navegadores web modernos.
El ESP32 tiene capacidad Bluetooth y BLE (Bluetooth de baja energía). Con el SDK adecuado, puedes programar el ESP32 para que actúe como un dispositivo Bluetooth que pueda comunicarse con otras plataformas, incluidos navegadores web que admitan API de Bluetooth.
WebSockets permite una comunicación bidireccional entre un cliente web y un servidor. Podrías programar el ESP32 para que actúe como un servidor WebSocket y permita que las aplicaciones web se comuniquen con él en tiempo real.
En el Arduino o ESP32, programarás la lógica que necesitas para interactuar con la interfaz web. Por ejemplo, si desde la interfaz decides encender un LED, el microcontrolador recibirá ese comando y encenderá el LED.
Electrónica para makers - guía completa. De paolo Aliverti
Learn Electronics with Arduino An Illustrated Beginner's Guide to Physical Computing - Jody Culkin, Eric Hagan
La directiva NgTemplateOutlet de Angular es una herramienta poderosa que te permite crear contenido dinámico y reutilizable en tus aplicaciones. Se utiliza principalmente para insertar una plantilla (template) dentro de la vista, y puede recibir información adicional mediante el paso de contexto. Aquí tienes una lista de algunas cosas que puedes hacer con NgTemplateOutlet:
Para utilizar NgTemplateOutlet en tu aplicación Angular, simplemente debes agregar la directiva ngTemplateOutlet al elemento del DOM donde deseas insertar la plantilla, y proporcionar la referencia a la plantilla que quieres utilizar.
<ng-template>
:<!-- app.component.html -->
<div>
<ng-container *ngTemplateOutlet="miTemplate; context: miContexto"></ng-container>
</div>
<ng-template #miTemplate let-mensaje="mensaje">
<p></p>
</ng-template>
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
miContexto = {
mensaje: '¡Hola, mundo!',
};
}
En este ejemplo, hemos creado una plantilla llamada miTemplate
que muestra un mensaje. La directiva *ngTemplateOutlet
se utiliza para insertar la plantilla miTemplate
dentro de un contenedor <ng-container>
. Además, hemos pasado el contexto miContexto
a la plantilla, que contiene un mensaje que se muestra en el párrafo <p>
.
Este ejemplo es bastante simple, pero puedes ampliarlo y combinarlo con otras características y directivas de Angular para crear contenido más dinámico y reutilizable.
En este ejemplo, vamos a crear una lista de usuarios con información básica y un botón para mostrar más detalles de cada usuario. Cuando se haga clic en el botón, se mostrará una tarjeta con la información adicional del usuario.
<!-- user-list.component.html -->
<div *ngFor="let user of users">
<h3></h3>
<p></p>
<button (click)="selectedUser = selectedUser === user ? null : user">Mostrar detalles</button>
<ng-container *ngTemplateOutlet="userDetails; context: { user: selectedUser === user ? user : null }"></ng-container>
</div>
<ng-template #userDetails let-user="user">
<ng-container *ngIf="user">
<div class="user-details">
<p>Dirección: </p>
<p>Teléfono: </p>
</div>
</ng-container>
</ng-template>
selectedUser
:// user-list.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.scss'],
})
export class UserListComponent {
users = [
{ name: 'Juan Pérez', email: 'juan@example.com', address: 'Calle 123', phone: '555-1234' },
{ name: 'María Rodríguez', email: 'maria@example.com', address: 'Calle 456', phone: '555-5678' },
// ...
];
selectedUser = null;
}
En este ejemplo, hemos creado un componente llamado UserListComponent
que muestra una lista de usuarios y utiliza la directiva *ngFor
para iterar sobre el arreglo users
. Para cada usuario, se muestra su nombre, correo electrónico y un botón para mostrar más detalles.
Cuando se hace clic en el botón "Mostrar detalles", la variable selectedUser
se actualiza con el usuario seleccionado. La directiva *ngTemplateOutlet
se utiliza para insertar la plantilla userDetails
en la vista y pasar el contexto que contiene el usuario seleccionado.
La plantilla userDetails
muestra la información adicional del usuario (dirección y teléfono) solo si el usuario seleccionado coincide con el usuario actual de la iteración. De lo contrario, no se muestra nada.
<!-- card.component.html -->
<div class="card">
<h3 class="card-title"></h3>
<div class="card-content">
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>
</div>
// card.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss'],
})
export class CardComponent {
@Input() title: string;
@Input() contentTemplate: TemplateRef<any>;
}
<!-- app.component.html -->
<app-card [title]="'Tarjeta personalizada'" [contentTemplate]="customCardTemplate"></app-card>
<ng-template #customCardTemplate>
<p>Este es el contenido personalizado de la tarjeta.</p>
<button (click)="onButtonClick()">Presiona aquí</button>
</ng-template>
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
onButtonClick() {
alert('Botón personalizado presionado!');
}
}
En este ejemplo, hemos creado un componente genérico de tarjeta llamado CardComponent
que acepta un título y una plantilla de contenido como entradas. La plantilla de contenido se inserta en la tarjeta utilizando la directiva *ngTemplateOutlet
.
El componente AppComponent
utiliza el componente de tarjeta y proporciona una plantilla personalizada llamada customCardTemplate
para cambiar la apariencia y el comportamiento de la tarjeta. La plantilla personalizada incluye un párrafo y un botón con un evento de clic que llama al método onButtonClick
en el componente AppComponent
.
<!-- product-table.component.html -->
<table>
<thead>
<tr>
<th>#</th>
<th>Nombre del producto</th>
<th>Precio</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let product of products; let i = index">
<ng-container *ngTemplateOutlet="rowTemplate; context: { $implicit: product, index: i }"></ng-container>
</ng-container>
</tbody>
</table>
<ng-template #rowTemplate let-product let-i="index">
<tr [class.highlight]="i % 2 === 0">
<td></td>
<td></td>
<td></td>
</tr>
</ng-template>
// product-table.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-product-table',
templateUrl: './product-table.component.html',
styleUrls: ['./product-table.component.scss'],
})
export class ProductTableComponent {
products = [
{ name: 'Producto A', price: 100 },
{ name: 'Producto B', price: 200 },
{ name: 'Producto C', price: 150 },
// ...
];
}
En este ejemplo, hemos creado un componente llamado ProductTableComponent
que muestra una tabla con información sobre productos y sus precios. Utilizamos la directiva *ngFor para iterar sobre el arreglo products
y crear una fila para cada producto en la tabla.
La directiva *ngTemplateOutlet se utiliza junto con *ngFor para insertar la plantilla rowTemplate
en la tabla para cada producto. Pasamos el contexto que contiene el producto actual y su índice a la plantilla.
En la plantilla rowTemplate
, utilizamos las variables product
e i
(índice) para personalizar el contenido de cada fila. Aplicamos una clase CSS highlight
a las filas pares (índice par) para resaltarlas. También mostramos el número de fila (índice + 1), el nombre del producto y su precio en formato de moneda.
<!-- card.component.html -->
<div class="card" [ngStyle]="{'background-color': backgroundColor}">
<h2></h2>
<ng-container *ngTemplateOutlet="contentTemplate"></ng-container>
</div>
// card.component.ts
import { Component, Input, TemplateRef } from '@angular/core';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss'],
})
export class CardComponent {
@Input() title: string;
@Input() backgroundColor: string;
@Input() contentTemplate: TemplateRef<any>;
}
<!-- app.component.html -->
<div style="display: flex">
<ng-template #content1>
<p style="color: red;">Este es el contenido personalizado con letras rojas.</p>
</ng-template>
<ng-template #content2>
<p style="color: blue;">Este es el contenido personalizado con letras azules.</p>
</ng-template>
<app-card title="Tarjeta 1" backgroundColor="lightblue" [contentTemplate]="content1"></app-card>
<app-card title="Tarjeta 2" backgroundColor="lightgreen" [contentTemplate]="content2"></app-card>
</div>
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {}
En este ejemplo, hemos creado dos componentes: CardComponent
y AppComponent
. El componente CardComponent
es un componente genérico que muestra una tarjeta con un título y contenido personalizado. El componente AppComponent
es el componente principal que contiene dos instancias del componente CardComponent
con diferentes colores de fondo y estilos de letra.
Utilizamos la directiva *ngTemplateOutlet en el componente CardComponent
para insertar el contenido personalizado desde el componente AppComponent
. Las plantillas de contenido se definen dentro de las etiquetas
Es como el ejemplo anterior, pero le incorporamos un objeto, más completo.
// content-data.interface.ts
import { TemplateRef } from '@angular/core';
export interface ContentData {
template: TemplateRef<any>;
textColor: string;
}
<!-- card.component.html -->
<div class="card" [ngStyle]="{'background-color': backgroundColor}">
<h2></h2>
<ng-container *ngTemplateOutlet="contentData.template; context: {textColor: contentData.textColor}"></ng-container>
</div>
// card.component.ts
import { Component, Input } from '@angular/core';
import { ContentData } from '../interfaces/content-data.interface';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss'],
})
export class CardComponent {
@Input() title: string;
@Input() backgroundColor: string;
@Input() contentData: ContentData;
}
<!-- app.component.html -->
<div style="display: flex">
<ng-template #content1 let-textColor="textColor">
<p [style.color]="textColor">Este es el contenido personalizado con letras rojas.</p>
</ng-template>
<ng-template #content2 let-textColor="textColor">
<p [style.color]="textColor">Este es el contenido personalizado con letras azules.</p>
</ng-template>
<app-card title="Tarjeta 1" backgroundColor="lightblue" [contentData]="{ template: content1, textColor: 'red' }"></app-card>
<app-card title="Tarjeta 2" backgroundColor="lightgreen" [contentData]="{ template: content2, textColor: 'blue' }"></app-card>
</div>
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {}
En este ejemplo, hemos creado una interfaz ContentData
que contiene una referencia a la plantilla y el color del texto. Luego, ajustamos los componentes CardComponent
y AppComponent
para utilizar esta nueva estructura de datos.
El componente CardComponent
ahora acepta un objeto ContentData
como entrada en lugar de una TemplateRef directamente. En el componente AppComponent
, creamos objetos ContentData
para cada tarjeta y los pasamos como entrada a los componentes CardComponent
.
La directiva
NgComponentOutlet
de Angular se utiliza para dinámicamente cargar y renderizar componentes en una plantilla Angular. Algunas de las cosas que se pueden hacer con esta directiva son:
// hello.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-hello',
template: '<p>Hello, World!</p>'
})
export class HelloComponent {}
// goodbye.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-goodbye',
template: '<p>Goodbye, World!</p>'
})
export class GoodbyeComponent {}
// app.component.ts
import { Component, Type } from '@angular/core';
import { HelloComponent } from './hello.component';
import { GoodbyeComponent } from './goodbye.component';
@Component({
selector: 'app-root',
template: `
<button (click)="switchComponent()">Switch Component</button>
<ng-container *ngComponentOutlet="currentComponent"></ng-container>
`,
})
export class AppComponent {
currentComponent: Type<any>;
private isHelloComponent: boolean = true;
constructor() {
this.currentComponent = HelloComponent;
}
switchComponent() {
this.isHelloComponent = !this.isHelloComponent;
this.currentComponent = this.isHelloComponent ? HelloComponent : GoodbyeComponent;
}
}
entryComponents
del módulo principal (si estás utilizando Angular 8 o inferior):// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';
import { GoodbyeComponent } from './goodbye.component';
@NgModule({
declarations: [
AppComponent,
HelloComponent,
GoodbyeComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent],
entryComponents: [HelloComponent, GoodbyeComponent]
})
export class AppModule { }
En este ejemplo, se crea un botón que, al hacer clic, cambia el componente que se muestra en el contenedor usando la directiva ngComponentOutlet
. Los componentes HelloComponent
y GoodbyeComponent
se cargarán de forma dinámica en función de la lógica de la aplicación, en este caso, alternándolos cada vez que se hace clic en el botón.
Tutorial, consejos y primeros pasos para el desarrollo de PWA. Doy ideas muy generales y básicas, que considero esenciales en la realizacion de las mismas. En el siguiente post (2/2) se pasa a la práctica con
Workbox
.
Aplicación web creado en html, css y javascript principalmente, que se abre en navegador, capaz de trabajar sin conexión, para móvil o escritorio, optimiza la carga de la web con sus distintas estrategias de cacheado. Creado en 2015 por google.
Si quieres saber que navegadores lo soportan: can I use service workers
Si estás en un internet explorer que no sea edge, no va a funcionar, para el resto, muy seguro que si.
Un problema que veo a PWA es que está en continuo cambio, las webs oficiales de google cambian constantemente, siempre están con carteles de que no está actualizado y está pendiente de hacerlo.
Pertenece a navigator navigator.serviceWorker
, lo tienen incorporado ya todos los navegadores modernos tanto de móvil como escritorio.
Es un proxy programable, controlando las solicitudes de red de tu web.
install
es el primero que obtiene un service worker y solo sucede una vez.installEvent.waitUntil()
señala la duración y el éxito o fracaso de tu instalación.fetch
y push
hasta que se termine de instalar correctamente y su estado sea "activo".fetch
de una página no atravesarán un service worker a menos que la solicitud de la página en sí lo haya hecho. Por lo tanto, tendrás que actualizar la página para ver los efectos del service worker.clients.claim()
puede anular esta configuración predeterminada y tomar el control de las páginas no supervisadas..register()
, se descarga el primer service worker. Si tu secuencia de comandos no se descarga, no se analiza o arroja un error en su ejecución inicial, se rechaza la promesa de registro y se descarta el service worker.
-self
hace referencia al propio service worker.Register
–> install
–> (Error
or Activated
) –> Idle
–>(Active
or Terminated
)
Se indica a serviceWorker donde está el archivo. Solo se ejecutará una vez si el navegador no lo tiene registrado. Si se borra cache, o cambia de navegador, se volverá a ejecutar para proceder a su instalación local.
var url = window.location.href;
// para producción
var swLocation = '/miwpa/sw.js';
// si estamos en localhost
if ( navigator.serviceWorker ) {
if ( url.includes('localhost') ) {
swLocation = '/sw.js';
}
}
navigator.serviceWorker.register( swLocation );
El primer evento que recibe un service worker es install. se va a disparar cada la primera vez, y cada vez que haya cambios en ese listener. Pero aún no se activa.
Es el lugar adecuado para almacenar en caché todo lo que necesitas para poder controlar los clientes. La promesa que pasas a event.waitUntil() permite que el navegador sepa que la instalación se completó correctamente.
Si se rechaza la promesa, significa que no se completó la instalación y el navegador elimina el service worker.
// ejemplo de evento install
self.addEventListener('install', e => {
...
e.skipWaiting() // opcional. No recomendable ya que hace saltárselo sin esperas.
e.waitUntil( Promise.all([ promise1, promise2 ]) ); // opcional
});
Una vez que tu service worker esté listo para controlar clientes y administrar eventos funcionales como push
y sync
, recibirás un evento activate
. Sin embargo, eso no significa que se controlará la página desde la que se realizó la llamada a .register().
La primera vez que cargas la versión, no se procesa la solicitud. La configuración predeterminada es consistencia: si tu página se carga sin un service worker, tampoco lo harán los subrecursos. Si cargas la versión otra vez (en otras palabras, si actualizas la página), se controlará la solicitud. Tanto la página como la imagen atravesarán eventos fetch
.
// ejemplo de evento activate
self.addEventListener('activate', e => {
const respuesta = caches.keys().then( keys => {
keys.forEach( key => {
...
});
});
e.waitUntil( respuesta ); // opcional
})
Todo archivo que se cargue al html pasa por fetch, por lo que se utiliza para aceptar, denegar, cambiar cualquier archivo por otro.
self.addEventListener('fetch', (event) => {
//fetch event handler
});
Para usar correctamente los service workers es necesario tener conocimientos de promesas, de fetch (nueva interfaz para hacer peticiones Http) Intentando no alargar este tutorial, existen unas web que lo explican muy bien, en castellano y con ejemplos prácticos.
Encadenar funciones asíncronas, que esperen unos a otras, transformándolas en síncronas.
function sumarUno(num){
let promesa = new Promise ((resolve, reject)=> {
setTimeout ( ()=>{
resolve(num+1)
},800);
})
return promesa;
}
sumarUno(5)
.then(sumarUno)
.then(sumarUno)
.then(sumarUno)
.then(sumarUno)
.then(nuevoNumero=>{
console.log(nuevoNumero); // 10
})
.catch(error=>{
console.log('Error en promesa');
console.log(error);
})
Se ejecutan un array de funciones asyncronas a la vez. Una vez finalizado puede encadenarse otra promesa como resultado.
function sumarLento(numero){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(numero+1);
},800)
})
}
let sumarRapido = (numero)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(numero+1);
},300)
})
}
Promise.all(sumarLento(5),sumarRapido(10))
.then(respuestas=>{
console.log(respuestas);
})
Solo muestra 1 resultado, el que primero llegue. En este caso será sumarRapido.
Promise.race(sumarLento(5),sumarRapido(10))
.then(respuestas=>{
console.log(respuestas);
})
Permite recuperar recursos a través de la red. Javascript ha progresado en este sentido, ya que antes era tan complicado que se solía usar siempre jquery. Los frameworks han realizado sus propias versiones, como angularjs y angular 2x con http de httpClient.
Vs antigua de js (no usar)
const request= new XMLHttpRequest();
request.open('GET','https://reqres.in/api/users',true);
request.send(null);
request.onreadystatechange = function(state){
if(request.readyState===4){
const resp = JSON.parse(request.response);
console.log(resp);
}
}
Vs actual
fetch('https://reqres.in/api/users')
.then(resp => resp.text())
.then(respObj=> {
console.log(respObj);
console.log(respObj.page);
console.log(respObj.per_page);
})
// para reemplazar web por la actual. activar Cors.
fetch('https://wikipedia.org')
.then(resp => resp.text())
.then(html=> {
document.open();
document.write(html);
document.close();
})
let usuario= {
nombre:'Jose Luis',
edad: 36
};
fetch('https://reqres.in/api/users',{
method:'POST',
body: JSON.stringify(usuario),
headers: {
'Content-Type': 'application/json'
}
})
.then(resp=> resp.json())
.then(console.log)
.catch(error=>{
console.log('Error en la petición');
console.log(error);
});
// enlazo img con la imagen del html
const img = document.querySelector('img');
fetch('mi-imagen.png')
.then(resp=>resp.blob())
.then(imagen => {
let imgPath = URL.createObjectURL(imagen);
img.src = imagPath;
})
Se necesita un servidor para arrancar la app. Pueden ser cualquiera, pero este es muy liviano.
www.npmjs.com/package/http-server
Una vez instalado, ejecutar http-server -o
Se abrirá una ventana del nevegador.
Ya que el responsive, y el móvil es tan importante en las PWA, es necesario que en todo momento trabajemos desde dispositivos móviles o similar.
Se puede hacer de 3 maneras, desde el mismo navegador de pc, no pudiendo probar todo. Otra manera es desde un móvil real, y si no se tiene, virtualizando un dispositivo android.
En herramientas de desarrolladores (f12) - application - service worker:
waiting to active <skipWaiting>
. Si pulsamos en esa url se actualizará el service Worker, igual que si pulsaramos Update and reload
.En herramientas de desarrolladores (f12) - application - cache: Cache storage: es donde se guardan nuestros archivos cacheados. De esta forma cuando la web no tenga conexión, lo cargará del cache.
En herramientas de desarrolladores (f12) - application - clear storage: clear site data: es el método más eficaz para dejar localhost o la url donde estás como limpio, al volver a cargar todo empieza de nuevo, de 0.
Para ello debemos de tener habilitadas las opciones de desarrollador, que por defecto están ocultas. Depende de la versión de android, y de la capa de más que tenga, por ejemplo xiaomi dispone de su propia forma de mostrarla. La mayoría están en ajustes - sobre el teléfono - versión (o build). Se pulsa 8 veces.
Se abre el dispositivo emulado en pantalla. En ese dispositivo abre chrome escribe: localhost:8080.
Desde el pc, podemos hacer igual que el punto anterior:
chrome://inspect/#devices
.
En chrome, f12, Lighthouse
(antes llamado audit) disponemos de una herramienta para ver la calidad de cualquier web. Está separado en bloques, uno de ellos es service Worker. Si no tiene aparecerá en gris.
Esta herramienta es útil para poder mejorar la calidad de nuestras webs. Cualquier cambio que hagas en tu localhost puedes volver a pulsar y ver si se han solucionado los errores marcados.
Los bloques son:
Puedo decir que llegar a más de un 80 según en que sección es todo un logro. Ni youtube lo consigue, que es una WPA. Simplemente, intentar conseguir el mayor número verifando los errores, los cuales son muy claros.
Archivo JSON que proporciona los metadatos necesarios para que la PWA pueda comportarse de manera más similar a una aplicación nativa: se puede instalar en la pantalla de inicio y es capaz de realizar transiciones suaves en la pantalla de inicio.
Configurar el json significa describir cómo se verá su PWA en la pantalla de inicio del usuario, así como cómo se verá cuando el usuario inicie su aplicación por primera vez. Además, el comportamiento de la interfaz de usuario del navegador (si estará visible u oculta).
Compatible con Chrome, Edge, el navegador de Android, Chrome para Android, Firefox para Android y Samsung Internet. Es parcialmente compatible con Safari.
Se puede crear manualmente, aunque hay webs que nos facilitan el trabajo. Un ejemplo es: app-manifest.firebaseapp.com Y no solo eso, en dicha web, podemos añadir una imagen 512x512 y nos genera todas los tamaños necesarios, todo en un zip.
Si quieres saber más sobre el archivo manifest, consulta la siguente web: developer.mozilla.org/es/docs/Web/Manifest
En la raiz, creamos nuestro archivo manifest.json
{
"name": "PWA Misiones espaciales",
"short_name": "PWA espacial",
"lang": "es-ES",
"start_url": "/index.html",
"display": "standalone",
"theme_color": "#ff00ed",
"background_color": "#ff8200",
"icons": [
{
"src": "images/touch/icon-128x128.png",
"sizes": "128x128"
},
{
"src": "images/touch/icon-192x192.png",
"sizes": "192x192"
},
{
"src": "images/touch/icon-256x256.png",
"sizes": "256x256"
},
{
"src": "images/touch/icon-384x384.png",
"sizes": "384x384"
},
{
"src": "images/touch/icon-512x512.png",
"sizes": "512x512"
}
]
}
En index.html, tag head, añadimos este código, para referenciar manifest y configurar algunos metas necesarios (algunos navegadores no soportan todas las características de manifest)
<link rel="manifest" href="manifest.json">
<!-- Todo lo demás no es obligatorio -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="PWA Misiones espaciales">
<meta name="apple-mobile-web-app-title" content="PWA Misiones espaciales">
<meta name="theme-color" content="#ff00ed">
<meta name="msapplication-navbutton-color" content="#ff00ed">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="msapplication-starturl" content="/index.html">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="apple-touch-icon" sizes="128x128" href="/images/touch/icon-128x128.png">
<link rel="icon" sizes="192x192" href="icon-192x192.png">
<link rel="apple-touch-icon" sizes="192x192" href="/images/touch/icon-192x192.png">
<link rel="icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="apple-touch-icon" sizes="256x256" href="/images/touch/icon-256x256.png">
<link rel="icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="apple-touch-icon" sizes="384x384" href="/images/touch/icon-384x384.png">
<link rel="icon" sizes="512x512" href="/images/touch/icon-512x512.png">
<link rel="apple-touch-icon" sizes="512x512" href="/images/touch/icon-512x512.png">
Comprobamos si tenemos bien configurado nuestro archivo manifest.json:
Ya podemos ver, si está todo bien, de forma más visual nuestro archivo manifest, con sus colores, iconos, y resto de configuración.
Volvemos a probar Audit (chrome - devTools - audit). Podemos comprobar que tenemos nuestra PWA funcionando perfectamente.
Como podemos comprobar, uno de los fallos es 'Does not redirect HTTP traffic to HTTPS'. Es decir, nos indica que al ejecutarse en localhost y no en una url con https, no vamos a poder disfrutar de todo el potencial de un PWA.
Permite enviar mensajes desde un servidor a un navegador (no tiene porqué estar abierto). Se contempla en 2 fases
if ('Notification' in window && navigator.serviceWorker) {
// Mostrar el UI para dejar que el usuario acepte poder recibir notificaciones.
}
if (Notification.permission === "granted") {
/* Aquí es donde haces tu magia ;) */
} else if (Notification.permission === "blocked") {
/* El usuario ha negado previamente hacer notificaciones. No se puede hacer nada */
} else {
/* Preguntar si da permiso el usuario */
}
Antes de poder enviar cualquier notificación push hay que solicitar permiso. Esto no es configurable y debe ser enmascarado con un modal previo customizado para solicitarlo en un momento, desaconsejando al principio de la carga como la gran mayoría hace.
Notification.requestPermission(function(status) {
console.log('Notification permission status:', status);
});
Aunque esto no es lo habitual, ya que suele ser de cara al servidor, y no el propio cliente quien genere la notificación.
Tiene un título, un cuerpo y un icono. Se le puede añadir vibración (si usas móvil funcionará), así como la fecha en la que se va a recibir.
function displayNotification() {
if (Notification.permission == 'granted') {
navigator.serviceWorker.getRegistration().then(function(reg) {
var options = {
body: 'Aquí el cuerpo del mensaje',
icon: 'images/icons/icon-512x512.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
}
};
reg.showNotification('Esto es un título', options);
});
}
}
// hace que se muestre la notificación.
displayNotification()
Para ello hay que seguir unos pasos muy bien explicados en esta web. angular-university.io/angular-push-notifications
Desaconsejado, son muchos pasos que de otra manera te los puedes saltar.
manifest.json
.Workbox
es una colección de distintas librerías y herramientas creadas por Google y que nos ayudan en la creación y simplificación de service workers para nuestras Progressive Web Apps.
Ver post 2/2 de PWA.
Para más información en la web de ionic ionicframework.com/docs/angular/pwa
ng add @angular/pwa
Tutorial de uso de Workbox para agilizar la creación de PWA, aplicaciones web progresivas.
Es un set de bibliotecas
creadas cómo no, por Google, que ayudan a la creación de service workers para las PWA. Dispone de varias estrategias de cacheado ya predefinidas para poder manejar html, css,js, imágenes, etc. Es importar y usar, de una forma fácil.
Realmente se hace obligatorio su uso, ya que hacerlo todo manual como ya expuse en el anterior post, se hace un tanto difícil con un alto índice de fallos que hace perder mucho más tiempo XD.
Para hacerlo más amigable he creado un proyecto cool en github para que no sea una experiencia perturbadora.
Si deambulas en los escabrosos archivos, puedes ver en firebase-messaging-sw.js
que está todo workbox en él, en muy pocas lineas. Usa jquery, sin frameworks.
Dispone de firebase para notificaciones push, pero no vas a poderlo ver, ya que el único que puede mandar esas notificaciones soy yo, y no voy a envíar notificaciones a diario para chequearlo. Así amigo, te animo a que con unos pequeños pasos lo hagas tú mismo, lo explicaré, o quizás ya lo veas explicado, en futuros o presentes posts. Buscar por firebase.
Este ejemplo usa la estrategia más habitual, que es servir la web con todo su contenido, cachear hasta un máximo prefijado (se puede cambiar) y si falta internet lo muestra como si estuvieras con ella.
Para que se active el service worker, debes pasar al 100% del scroll para que salte. Acepta. Para eliminarlo luego pulsa en el candado de la izquierda en la barra de direcciones.
La guía oficial developers.google.com/web/tools/workbox/guides/get-started nos explica como crearlas de una manera fácil y cómoda, salvo que en mi opinión, a fecha de hoy (está en continuo cambio), no está en órden y faltan algunos datos, por eso este post.
Todo ejemplo en dicha web está preparada para usarlo con módulos. Qué pasa con los modules
en javascript? Que javascript vanilla no puede usarlo y si tu proyecto está en jquery o similar, deberás cargarlo de otra forma (mediante CDN, y con this.workbox, como indico más adelante.)
Hay 2 formas de importarlo.
La pongo primera ya que el tutorial oficial lo deja un poco de lado, pero su uso es menor, ya que está pensada para proyectos pequeños que no usen webpack, ni frameworks como angular.
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js');
Todos los ejemplos de la web usan los métodos sin el nombre de la clase, ni el namespace, por lo que hay que incluirlo.
ejemplo:
registerRoute(
debe ser reemplazado por: (es decir, incluir clase y namespace)
this.workbox.routing.registerRoute(
importScripts
.Todo va por módulos, si algo quieres, antes debes instalarlo mediante npm.
Aquí tienes todas los módulos actuales para instalar: developers.google.com/web/tools/workbox/modules.
Tan fácil como hacer npm install workbox-core
Si quieres instalarte los módulos más importantes, y guardarlos en package.json
npm install --save workbox-core workbox-background-sync workbox-expiration workbox-google-analytics workbox-navigation-preload workbox-precaching workbox-routing workbox-strategies
Con estos módulos, creo que no me falta ninguno importante, puedes hacer casi cualquier cosa. Si te falta alguno, instala de nuevo con --save
.
Todo service worker necesita su propio archivo manifest.json en raíz. Debes de crearlo. Esto ya lo explico detalladamente en el anterior post.
Cuando se va internet en casa o falta de cobertura en móvil, nos interesa que si hay una imágen que no tenemos, nos muestre una por defecto ya cacheada y si el link que queremos acceder no lo tenemos cacheado, que nos responda con una página por defecto. Difícil? Con workbox no!.
this.workbox.precaching.precacheAndRoute([{
url: 'offline.html',
revision: null
},
{
url: 'images2/no-image.png',
revision: null
},
// ... other entries ...
]);
// Catch routing errors, like if the user is offline
this.workbox.routing.setCatchHandler(async ({
event
}) => {
console.log('event', event);
console.log('event.request.destination', event.request.destination);
// Return the precached offline page if a document is being requested
if (event.request.destination === 'document') {
return this.workbox.precaching.matchPrecache('offline.html');
} else if (event.request.destination === 'image') {
return this.workbox.precaching.matchPrecache('images2/no-image.png');
}
return Response.error();
});
Si usas módulos, quita this.workbox.precaching y this.workbox.routing
En herramientas de desarrollo de Chrome, tab Application.
Manifest
, si todo está correcto, pruébalo en móvil. Borra cookies si te falla, instala app.github pages
, o firebase hosting
.Todo esto y mucho más en developer.chrome.com/docs/devtools/progressive-web-apps.
]]>Tutorial, consejos y guía de aprendizaje básico para MongoDB
Es un sistema de base de datos NoSQL, orientado a documentos y de código abierto. En lugar de guardar los datos en tablas, los guarda en BSON, similar a JSON, haciendo que el acceso sea mucho más rápido. Disponible para Linux, OS X, Windows. Ver más información: mongoDB en wikipedia. Entre los NoSQL están: MongoDB, Cassandra, HBase, Neo4j.
Un cluster puedes tener de 1 a 3 cluster (de forma gratuita, si no más), cada uno de ellos puede tener x databases, cada uno de ellos varios collectión y a su vez documents.
CLUSTER - DBS - COLLECTIONS - DOCUMENTS
.
Una comparativa de terminología usada en SQL frente a NOSQL (con MongoDB y Cassandra)
SQL | MongoDB | Cassandra |
---|---|---|
Table | Collection | Table |
Row | Document | Row |
Column | field | Column |
Primary key | ObjectId | Primary key |
Index | Index | Index |
View | View | Materialized view |
Nested table or object | Embedded document | Map |
Array | Array | List |
En la web oficial university.mongodb.com hay un montón de cursos 100% gratuitos, en inglés subtitulados en castellano. Se entienden muy bien, se hace rápido y te dan titulación oficial. Cada curso tiene un nivel, y hay cursos que son dependientes de otros más básicos.
De todos estos destaco los que más me gustan:
No te preocupes por su instalación, no necesita, usa atlas, en la nube.
Es un servicio de Base de Datos en la nube, que te permite crear, administrar la BBDD. Esto evita tener que instalar nada, mantener un servidor.
Como todo servicio tiene su coste, pero hay una modalidad gratuita, que es para equipos de aprendizaje y desarrolladores de pequeñas aplicaciones. Dispones de 512mb de almacenamiento, de sobra para cualquier mini proyecto que estés empezando y puedes usar hasta 3 server replica set y lo mejor de todo, sin límite de caducidad, nunca se borrará.
Para acceder al entorno, entrar en mongodb.com/cloud/atlas. Puedes acceder con la cuenta de google.
Para trabajar antes deberás crear:
Importante cuando te pregunta de la lista (whitelist) de direcciones ip, poner Allow Access from Anywhere
.
Añadir usuario y contraseña.
En tu cluster puedes hacer 3 cosas básicamente (pulsando a conectar):
shell
: usando una interface de mongodb en javascript. Puedes crear una máquina virtual (de pago), o usar tu propio shell (debes instalarlo en tu pc)MongoDB Compass
: explora, modifica y visualiza tu BBDD mediante esta GUI.En la instalación de MongoDB server ya viene incluido el shell. Alternativamente puedes descargar separadamente solo el shell desde la web de documentación docs.mongodb.com seguir las instrucciones. Básicamente es:
sudo systemctl start mongod
sudo systemctl status mongod
sudo systemctl enable mongod
En cluster de atlas, pulsar connect, pulsar que tienes un shell instalado. Copiar la linea:
mongo "mongodb+srv://USUARIO:CONTRASENIA@CLUSTER/COLLECTION"
mongo "mongodb+srv://m001-student:m001-mongodb-basics@sandbox.wzwpg.mongodb.net/myFirstDatabase"
No es la única, si la oficial. otra opción es Robo 3T
. robomongo.org
Compass es una herramienta muy completa para usar el lenguaje MQL de una manera muy cómoda. Visualización de databases, colecciones, documentos, todo muy visual, muy vistoso y con muchas opciones. Tanto si estás aprendiendo, como si ya controlas, esta herramienta es en mi opinión, obligatoria. Para todos los sistemas operativos. mongodb.com/download/compass
Trabajar en terminal puede resultar un poco duro, Atlas UI quizás no es lo que buscas.
Una de las formás que más me gusta es desde visual studio code y conectarme al cluster de Atlas. Para ello debes de instalar el plugin MongoDB for VS Code
. Se habilitará un icono en forma de hoja vertical a la izquierda.
Es ahí donde añades la conexión, que actualmente y esto puede cambiar a mejor siempre, tienes 2 sistemas, por la uri (todo en una misma url) o del método tradicional con host, usuario, contraseña…. personalmente prefiero la primera.
Puedes conectarte remotamente a atlas, o a un mongo que tengas local.
Una vez conectado, puedes hacer scripts, lo bueno es que lo colorea y visualmente se ve muy bien. Para ello ctrl+shift+p MongoDB: Create MongoDB Playground
. Puedes usar doble barra para comentarios.
//select the database tu use
show dbs
use('sample_training')
show collections
db.inspections.insert({"id":"12345-ttt",certificate_number:1234,"business_name":"abcde"})
db.inspections.find({}).sort({_id:-1}).limit(1)
Arriba a la derecha hay un icono de play. Al pinchar se muestra una ventana colorida del resultado (json).
Otro punto es que tiene autocompletado de colecciones y demás. Para más información hay varios videos en youtube: How to Use Visual Studio Code as Your MongoDB IDE.
JSON | BSON |
---|---|
codificado en utf8 string | codificado en binario |
tipos: String, Boolean, Number,Array | tipos: String, Boolean, Number (Integer, Long, Float,…), Array, Date, Raw Binary |
legible por humanos y máquinas | sólo máquinas. |
mongoimport | mongorestore |
mongoexport | mongodump |
Es decir, guardamos en BSON (con un soporte de tipos más enriquecido), pero lo leemos como un JSON.
Una vez creado un cluster, y db, en el botón de los 3 puntitos, seleccionar Load Sample Dataset
. Se descargará varias databases con sus respectivos collections. Ocupa 350mb, como el límite gratuito son de 500mb solo podrás tener 1 ejemplo importado.
Para conocer más a fondo todas las opciones que tiene cada uno, en consola, usar --help
. En consola escribe mongo y tabulador, verás que hay otros pocos comandos más, como monodecript, mongofiles, mongokerberos
mongoexport --help
mongoimport --uri "<Atlas Cluster URI>" --drop miColeccion.json --collection miColeccion
mongoimport --uri "mongodb+srv://m001-student:m001-mongodb-basics@sandbox.wzwpg.mongodb.net/sample_supplies" --drop sales.json --collection sales
Si no se especifica la colección, la tomará del propio archivo json. --drop
hace borrar la anterior colección e incorporar la nueva. Si no lo ponemos e insertamos, si tienen la misma id, dará error de inserción en cada documento.
mongoexport --uri "<Atlas Cluster URI>" --collection miColeccion --out nombreArchivo.json
Guarda en nombreArchivo.json la colección miColeccion del cluster del uri.
mongodump
.mongorestore --uri "<Atlas Cluster URI>" --drop directorio_Donde_se_aloja_BSON
mongorestore --uri "mongodb+srv://m001-student:m001-mongodb-basics@sandbox.wzwpg.mongodb.net/sample_supplies" dump/sample_supplies
Si no se ha tocado nada, y no hay diferencias, dará un error. Puedes probar a borrar la bd para recuperarla de nuevo.
Donde dump es la carpeta donde se guardó con mongodump. En este caso recuperamos toda la db, que solo tiene una colección pero pudiera tener muchas más.
mongodump --uri "<Atlas Cluster URI>"
mongodump --uri "mongodb+srv://m001-student:m001-mongodb-basics@sandbox.wzwpg.mongodb.net/sample_supplies"
En mi ejemplo, guarda dentro de la carpeta dump
la database(db) sample_supplies la cual contiene una sola colección "sales".
Desde Atlas, mediante data explorer Clickando en collections.
Un namespace es una concatenación de database + collection.
Desde shell desde tu pc o máquina virtual.
mongo "mongodb+srv://m001-student:m001-mongodb-basics@sandbox.wzwpg.mongodb.net/admin"
mongo "mongodb+srv://<username>:<password>@<cluster>.mongodb.net/admin"
Una vez accedido, se puede usar los siguientes comandos:
show dbs
: muestra todas las databaseuse
databaseExample: acceder a la databaseshow collections
: muestra todas sus colecciones.Desde Atlas UI
En clusters, selecciona una db, click en el collection a insertar, botón insert document
. Pulsando + o tab se pasa al siguiente.
Desde shell
show dbs
use miDataBase
show collections
db.inspections.insert({"id":"12345-ttt",certificate_number:1234,"business_name":"abcde"})
db.inspections.find({}).sort({_id:-1}).limit(1)
No hace falta añadir el atributo _id
, ya que así se genera automáticamente.
A tener en cuenta: _id
es el identificador único de un documento en una colección. Es requerido en cada documento de MongoDB. Si usamos ObjectId()
, MongoDB creará una id única, por lo que es lo más recomendable, y no crear nosotros mismos una id ya sea string o number.
Operaciones básicas para documentos de colecciones. Crear, leer, actualizar y borrar documentos de colecciones de databases.
Las operaciones más básicas las puedes ver en docs.mongodb.com/manual/crud/
find
(básico) o aggregate
(es un completo framework que funciona a modo tuberías - pipes)updateOne
, updateMany
y replaceOne
.deleteOne
y deleteMany
.insertOne
y insertMany
.Una forma más pro de realizar consultas, en vez de find, se usa aggregation.
Más información en docs.mongodb.com/manual/aggregation.
Lo que hace es por bloques, de una forma más organizada, se ve mucho mejor, más fácil de mantener. Personalmente lo veo un código mucho más limpio.
db.orders.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])
Una forma de probarlo muy visual es con Compass
, con solo hacer clicks, dificil equivocarse, con un entorno muy agradable.
Se que explico mucho y nada. Me gustaría que te hayas quedado con estas nociones generales, es una visión general de MongoDB, que sin duda, si la sigues puedes aprender mucho. Espero que te haya servido. Ten mucho en cuenta las url, que pueden haber cambiado en su tiempo, pero la idea es lo que importa. La api de Mongo es muy útil, y a diferencia de otras, esta tiene las 3 b: buena, bonita y barata.
]]>Guia para hacer componentes dinámicos en Angular 9, una manera más rápida y limpia, con menos lineas y sin tocar los módulos.
Ha cambiado ligeramente la manera de incorporar componentes dinámicos a nuestro Angular con la nueva llegada de la versión 9. Nos olvidamos de añadir al módulo mediante entryComponents
los componentes que se van a incorporar dinámicamente después. Ahora todo se carga en caliente desde el componente mediante async await
.
No ha cambiado nada. Justo donde se quiere añadir dicho componente dinámico, añadir una template reference variable
para tener una referencia al elemento.
En el html:
...
<ng-container #componenteDinamico></ng-container>
Podría ser un div, pero se crearía un padre div estuviera creado o no dicho componente dinámico. Para eliminar ese div innecesario.
Todo lo demás se hace en el ts.
ViewChild
de tipo ViewContainerRef
.En el ts:
export class ItemsListComponent implements OnInit, OnDestroy, AfterViewInit {
miFactory: ComponentFactory<any>;
componentRef: ComponentRef<miDinamicoComponent> = null; // se declara una variable referencia.
componentRef2: ComponentRef<miDinamicoComponent2> = null; // se declara una variable referencia.
// ViewContainerRef crea componentes en su interior de forma dinámica
// Hay que añadir read: ViewContainerRef, ya que si no, devolvería un ElementRef,
// que es lo que devuelve por defecto un viewChild.
// la referencia que tenemos es compDynamicContainer, y en ese contenedor añadiremos los componentes dinámicos
@ViewChild('componenteDinamico', { read: ViewContainerRef }) compDynamicContainer: ViewContainerRef;
Si lo que queremos es cambiar toda la página, no hace falta entonces viewchild. solo habría que añadir en el constructor:
constructor(
...
private compDynamicContainer: ViewContainerRef,
) { }
Esta parte tampoco ha cambiado. Inserta el servicio ComponentFactoryResolver
, el cual permite fabricar componentes de forma dinámica.
En el constructor:
constructor(
...
private resolver: ComponentFactoryResolver
) { }
Es aquí donde cambia todo en Angular 9. Imagina 2 botones, dos funciones async que cada uno carga un componente distinto, y 2 botones, cada uno ejecuta una función async. Pues cambiando esos botones se cambiaría el componente, en la zona del dom donde refleja viewchild.
Crea una factoría para el componente dinámico, que permita instanciarlo en el elemento contenedor creado anteriormente (compDynamicContainer).
ngAfterViewInit() {
this.getLazyComponent();
}
async getLazyComponent() {
this.compDynamicContainer.clear();
const { MiComponenteComponent } = await import('@miscomponentes/components/mi-componente/mi-componente.component');
this.componentRef = this.compDynamicContainer.createComponent(
this.resolver.resolveComponentFactory(MiComponenteComponent)
);
}
En este punto el componente se ha creado y se está visualizando. Si se quiere cargar en otro momento, incorporarlo en una función en vez de ngAfterViewInit
.
Una vez guardada la referencia de la creación del componente, se puede acceder a dicho componente, ejecutar métodos públicos y acceder a sus propiedades.
Todo esto gracias a instance
.
this.componentRef.instance.miFuncionSuma(15,2)
this.componentRef.instance.titulo='Cambio el titulo del componente';
this.componentRef.instance.mensaje='cambio el mensaje también';
También se puede acceder e incorporar a cualquier @Input, ya que es público. En el caso de los @Output, nos podemos subscribir a ellos:
this.componentRef.instance.miEventEmiter(()=>{
console.log('se ejecuta el event emiter');
});
No ha cambiado nada de la última versión.
Supongamos que tenemos un contenedor compDynamicContainer
, y queremos que en un primer momento se pinte un componente, pero que a los x segundos, o al pulsar cualquier acción, se elimine, o se cambie por otro componente. Esto puede ser si el componente dinámico es un modal, o tenemos un sistema de paginado con componentes dinámicos.
Esto se hace con destroy()
. Un ejemplo:
ngAfterViewInit() {
this.getLazyComponent();
setTimeout(() => {
this.getLazy2Component();
}, 2000);
}
async getLazyComponent() {
this.componentRef.destroy();
this.compDynamicContainer.clear();
const { miDinamicoComponent } = await import('@miscomponentes/components/mi-componente/mi-componente.component');
this.componentRef = this.compDynamicContainer.createComponent(
this.resolver.resolveComponentFactory(AntsListComponent)
);
this.componentRef.instance.openCard(24);
}
async getLazy2Component() {
this.componentRef.destroy();
this.compDynamicContainer.clear();
const { miDinamicoComponent } = await import('@miscomponentes/components/mi-componente/mi-componente2.component');
this.componentRef = this.compDynamicContainer.createComponent(
this.resolver.resolveComponentFactory(AntsListComponent)
);
this.componentRef.instance.card(10);
}
Guía de primeros pasos en vuejs 3
Guía oficial de la versión 3.x
$ npm install -g @vue/cli
# o
$ yarn global add @vue/cli
Además instalamos los siguientes:
$ npm i -g @vue/cli-plugin-babel @vue/cli-plugin-typescript @vue/cli-plugin-unit-mocha eslint-plugin-vue @vue/cli-service
$ vue create miapp
Elije manual select. y añade las características que quieras
$ npm run serve
$ npx vue-cli-service serve
Vue no dispone de un cli tan completo como tiene Angular. Es por ello que la generación de los componentes han de ser a mano.
Se crea un objeto de tipo vue, donde se indica el nombre de el componente.
En data
se almacena las variables scope que se necesitan en el html.
Un ejemplo de un componente, que dispone de su ts y el html.
mi-componente.html
<div id="app">
<div v-if="mostrar">
<img v-bind:src="imagen" />
<img :src="imagen" />
<!-- forma abreviada -->
</div>
</div>
<div v-if="mostrar">
<img :src="imagen" />
</div>
<button v-on:click="toggleMostrar">Mostrar/Ocultar</button>
<button @click="toggleMostrar">Mostrar/Ocultar</button>
<!-- forma abreviada -->
mi-componente.ts
const app = new Vue({
el: "#app",
data: {
mostrar: true,
mensaje: "Hola Vue!",
imagen:
"http://.../una-imagen.jpg"
},
methods: {
toggleMostrar: function() {
this.mostrar = !this.mostrar;
}
}
});
<div id="app" />;
const app = new Vue({
el: "#app",
template: `
<div>
<div v-if="mostrar">
<img :src="imagen" />
</div>
<button @click="toggleMostrar">Mostrar/Ocultar</button>
</div>
`,
data: {
mostrar: true,
mensaje: "Hola Vue!",
imagen:
"http://.../una-imagen.jpg"
},
methods: {
toggleMostrar: function() {
this.mostrar = !this.mostrar;
}
}
});
<template>
y separando el código del htmlhtml
<div id="app"></div>
<template id="ejemplo">
<div>
<div v-if="mostrar">
<img :src="imagen" />
</div>
<button @click="toggleMostrar">Mostrar/Ocultar</button>
</div>
</template>
vue
const app = new Vue({
el: '#app',
template: '#ejemplo',
data: {
...
},
methods: {
...
}
})
vue
const app = new Vue({
el: "#app",
template: '#ejemplo',
data: {
cursos: [
{
name: "curso 1",
url: "http://.../una-imagen.jpg"
},
{
name: "curso 2",
url:
"http://.../una-imagen.jpg"
},
{
name: "curso 3",
url: "http://.../una-imagen.jpg"
}
]
}
});
html
<div id="app">
<ul>
<li v-for="(curso, index) in cursos" :key="index">
<a :href="curso.url"></a>
</li>
</ul>
</div>
Vue VS Code Extension Pack
Vetur
npm install -g eslint
eslint --init
Vue
no tiene un cli tan bueno como el de Angular
. Aún no dispone de generador de componentes.
Puedes probar esta biblioteca
# $ npm install -g vue-generate-component
$ npm install -g vue-generate-component-typescript
Crea un componente:
$ vgc footer
y lo mueves a la carpeta correcta.
]]>Guía de buenas prácticas de comunicación entre componentes. Transclusión (ng-content), elementRef, componentes dinámicos, @ViewChild, @ContentChild, BehaviorSubject, Subject, Renderer2 …
Inclusión de un componente o parte del mismo dentro de otro componente. En AngularJs lo conocemos por ngTransclude
.
Un ejemplo claro es un componente modal que tiene unos estilos definidos, un título header, un texto body y una zona donde hay un número x de botones.
De esta manera nos ahoramos Outputs, eventEmitter y un complicado componente de condicionales para todas las casuísticas del proyecto, dejando que el componente padre que llame a dicho componente modal.
En este caso se usa select con atributos []
, aunque se puede usar mediante clases .
o como un tag directamente.
Componente modal
<div>
<div class="background"></div>
<div class="card">
<div class="header">
<ng-content select="[header]"></ng-content>
</div>
<div class="message">
<ng-content select="[message]"></ng-content>
</div>
<div class="footer">
<ng-content select="button"></ng-content>
</div>
</div>
</div>
Desde un componente padre, llama a la modal y dentro añade su propio header, body y botones. En este caso los estilos los maneja el padre, aplicando unos estilos diferentes a los definidos desde el componente modal.
Componente padre
<app-mi-modal [hidden]="!isModalVisible">
<div header>Esto es un título</div>
<div message><label>Este es el mensaje</label></div>
<input [(ngModel)]="numero" type="number" #numeroInput> <!-- template reference variable -->
<button (click)="cancelarModal()" class="cancel-button">Cancelar</button>
<button (click)="aceptarModal()" class="ok-button">Aceptar</button>
</app-mi-modal>
Para la comunicación entre distintos componentes, hay que seguir distintas estrategias según las posiciones entre ellos. Por ello no es lo mismo tratar un componente padre a un hijo, y viceversa.
A parte de los property binding
mediante decorador @Input, event binding
mediante decorador @Output, y los servicios con provider en módulo, angular nos proporciona una serie de técnicas que optimizan mucho el código.
@ContentChild
o @ContentChildren
.Componente padre
export class FatherComponent {
constructor() { }
suma(num1: number, num2: number): number {
return num1 + num2;
}
resta(num1: number, num2: number): number {
return num1 - num2;
}
Componente hijo
import { FatherComponent } from '../FatherComponent/FatherComponent.component';
...
export class ChildrenComponent {
constructor(public father: FatherComponent) { }
miMetodo() {
const a = this.father.suma(2, 3);
const b = this.father.resta(2, 3);
}
template reference variable
(desde html)Permite referenciar un componente hijo, y llamarlo desde el propio template, teniendo acceso a todos sus métodos públicos.
<app-mi-componente #micomponente></app-mi-componente>
<button (click)="micomponente.metodoPublico()">mi botón</button>
@ContentChild
. con componente dentro de un ng-content
.
Usar ContentChild para obtener el primer elemento o la directiva que coincida con el selector del contenido DOM. Si el contenido del DOM cambia y un nuevo elemento secundario coincide con el selector, la propiedad se actualizará.Es más potente que template reference variable
, ya que permite acceder aparte del template, desde el propio typescript, pudiendo usar los métodos públicos del hijo desde el padre.
Importante No se puede tener @ContentChild desde el padre y inyección de dependencia desde el hijo al padre, la cual hace una dependencia circular, la cual no se puede resolver.
El contenido de un componente no está disponible para su padre hasta despues de su inicialización. Es por ello que se debe de utilizar el evento de ciclo de vida ngAfterContentInit()
;
@ContentChildren
si hay varios componentes iguales y están dentro de un ng-content
. Hay que usar QueryList
.En el ts
export class FatherComponent implements OnInit, AfterContentInit, OnDestroy {
@ContentChildren(ChildComponent) public myChildren:QueryList<ChildComponent>;
ngAfterContentInit(){
// llamar a los métodos de myChildren
}
En el html
<div class="content">
<ng-content></ng-content>
</div>
mediante @ViewChild
. Es como @ContentChild
pero sin estar dentro de ng-content
, directamente en el template del padre. Es decir, es como una template reference variable
pero del lado del typescript.
mediante @ViewChildren
pero para varios componentes iguales.
Cuando aumenta la lógica de un componente, a menudo la trasladamos a un servicio. Si se tiene un eventEmitter, desde el servicio no se puede hacer de la misma manera. Hay que crear un observable y desde el componente subscribirse para pasar el eventEmitter.
servicio
// Subject es un tipo especial de observable que permite que los valores se compartan a muchos observadores.
private clickadoSource = new Subject<void>();
public clickObs$ = this.clickadoSource.asObservable();
...
private clickar() {
this.clickadoSource.next();
}
// BehaviorSubject es como Subject, pero además se le envía un parámetro.
private numeroSource = new BehaviorSubject<number>(55);
public cambiaNumObs$ = this.numeroSource.asObservable();
...
private cambiaNum() {
// BehaviorSubject tiene métodos, entre ellos el de recuperación de valor
this.numeroSource.next(this.numeroSource.getValue()+1);
}
Componente que llama al servicio
this.aSubscription = this.miServicio.clickObs$
.subscribe(()=>{
this.miEventEmmiter.emit();
});
this.bSubscription = this.miServicio.cambiaNumObs$
.subscribe((data)=>{
console.log(data);
this.miEventEmmiter.emit(data);
});
El elementRef es una referencia que se obtiene a partir de un elemento generado con viewChild o contentChild.
Una vez creada la ViewChild
de tipo ElementRef
, se puede manipular el dom desde el método ngAfterViewInit
.
<input [(ngModel)]="time" type="number" #miInput>
@ViewChild("miInput") nombre: ElementRef;
ngAfterViewInit(){
console.log(this.nombre); // aparece la propiedad nativeElement y dentro una gran cantidad de propiedades y métodos
this.nombre.nativeElement.setAttribute('placeholder', 'Escriba su nombre');
this.nombre.nativeElement.addClass('una-clase');
this.nombre.nativeElement.focus();
}
Angular es platform agnostic, permite renderizar en varias plataformas que no sea navegador web, como es el caso de los Web Workers.
Es por ello que se debe evitar a toda costa el uso de las variables globales window
, document
, o manipular el DOM con ElementRef
.
constructor(private renderer: Renderer2) {
}
ngAfterViewInit(){
this.renderer.setAttribute(this.nombre.nativeElement, "placeholder", "Escriba su nombre");
this.renderer.addClass(this.nombre.nativeElement, 'una-clase');
this.renderer.selectRootElement(this.nombre.nativeElement).focus();
}
Mediante este etiqueta, agrupa código html que se puede reutilizar sucesivamente.
Una forma
<div *ngIf="datos else loading">
...
</div>
<ng-template #loading>
<div>Loading...</div>
</ng-template>
Otra forma
<ng-template [ngIf]="datos" [ngIfElse]="loading">
<div class="una-clase">
...
</div>
</ng-template>
<ng-template #loading>
<div>Loading...</div>
</ng-template>
Sin utilizar directivas *ngIf
, mediante directiva ngTemplateOutlet
:
<ng-template #saluda>
<div>Hola!!!</div>
</ng-template>
<ng-container [ngTemplateOutlet]="saluda"></ng-container>
<ng-container [ngTemplateOutlet]="saluda"></ng-container>
<ng-container [ngTemplateOutlet]="saluda"></ng-container>
Al poner un *ngIf
se crea en el dom un div inecesario. Actua como un nodo del DOM pero que en realidad no se renderiza. La forma de poder evitarlo es así.
<ng-container *ngIf="visible">
<div class="una-clase"></div>
...
</div>
</ng-container>
Esto se hace desde el módulo donde se hacen las declarations. Aclaro, si por ejemplo tenemos en el módulo shared el componente donde va a cargar x componentes dinámicos, sería shared.module.ts.
Se debe añadir a entryComponents
todos los componentes dinámicos
declarations: [
miDinamicoComponent,
miDinamicoComponent2
],
imports: [
...
],
entryComponents: [
miDinamicoComponent,
miDinamicoComponent2
]
De esta manera, angular sabe que en algún momento este componente se va a incluir al dom de forma dinámica.
Justo donde se quiere añadir dicho componente dinámico, añadir una template reference variable
para tener una referencia al elemento.
En el html:
...
<ng-container #componenteDinamico></ng-container>
Podría ser un div, pero se crearía un padre div estuviera creado o no dicho componente dinámico. Para eliminar ese div innecesario.
Todo lo demás se hace en el ts.
ViewChild
de tipo ViewContainerRef
.En el ts:
export class ItemsListComponent implements OnInit, OnDestroy, AfterViewInit {
miFactory: ComponentFactory<any>;
componentRef: ComponentRef<miDinamicoComponent> = null; // se declara una variable referencia.
componentRef2: ComponentRef<miDinamicoComponent2> = null; // se declara una variable referencia.
// ViewContainerRef crea componentes en su interior de forma dinámica
// Hay que añadir read: ViewContainerRef, ya que si no, devolvería un ElementRef,
// que es lo que devuelve por defecto un viewChild.
// la referencia que tenemos es compDynamicContainer, y en ese contenedor añadiremos los componentes dinámicos
@ViewChild('componenteDinamico', { read: ViewContainerRef }) compDynamicContainer: ViewContainerRef;
Insertar el servicio ComponentFactoryResolver
, el cual permite fabricar componentes de forma dinámica
En el constructor:
constructor(
...
private resolver: ComponentFactoryResolver
) { }
Crea una factoría para el componente dinámico, que permita instanciarlo en el elemento contenedor creado anteriormente (compDynamicContainer).
ngAfterViewInit() {
this.miFactory = this.resolver.resolveComponentFactory(miDinamicoComponent);
this.componentRef = this.compDynamicContainer.createComponent(miFactory);
}
En este punto el componente se ha creado y se está visualizando. Si se quiere cargar en otro momento, incorporarlo en una función en vez de ngAfterViewInit
.
Una vez guardada la referencia de la creación del componente, se puede acceder a dicho componente, ejecutar métodos públicos y acceder a sus propiedades.
Todo esto gracias a instance
.
this.componentRef.instance.miFuncionSuma(15,2)
this.componentRef.instance.titulo='Cambio el titulo del componente';
this.componentRef.instance.mensaje='cambio el mensaje también';
También se puede acceder e incorporar en un @Input, ya que es público. En el caso de los @Output, nos podemos subscribir a ellos:
this.componentRef.instance.miEventEmiter(()=>{
console.log('se ejecuta el event emiter');
});
Supongamos que tenemos un contenedor compDynamicContainer
, y queremos que en un primer momento se pinte un componente, pero que a los x segundos, o al pulsar cualquier acción, se elimine, o se cambie por otro componente. Esto puede ser si el componente dinámico es un modal, o tenemos un sistema de paginado con componentes dinámicos.
Esto se hace con destroy()
. Un ejemplo:
ngAfterViewInit() {
this.miFactory = this.resolver.resolveComponentFactory(miDinamicoComponent);
this.componentRef = this.compDynamicContainer.createComponent(this.miFactory);
this.componentRef.instance.openCard(24);
// en 2 segundos destruimos el componente 1 e incorporamos el componente 2.
setTimeout(() => {
this.componentRef.destroy();
this.miFactory = this.resolver.resolveComponentFactory(miDinamicoComponent2);
// En la referencia componentRef, creamos el componente dinámico dentro del contenedor compDynamicContainer
// el tipo de componente dinámico lo asigna la factoría
this.componentRef2 = this.compDynamicContainer.createComponent(this.miFactory);
}, 2000);
}
Tutorial para obtener todas las interfaces de un API generado por swagger y expórtalo de forma instantanea en un proyecto Angular.
Mediante la herramienta swagger-codegen, generamos todas las interfaces de un API que disponga de swagger en un solo click, rápido y sencillo. Si no tienes ninguna ahora no te preocupes, usaremos una gratuita online.
Swagger Codegen te ayuda en la genereción automática de modelo y servicios, olvida escribirlo a mano.
Swagger es el marco de herramientas más grande del mundo para la especificación OpenAPI (OEA). Es el estándar de facto para las API reutilizables y mantenibles. El conjunto de herramientas facilita enormemente el dolor de documentar e interactuar con las API.
OpenAPI = Especificación.
Swagger = Herramientas para implementar la especificación.
La lista de lenguajes y marcos compatibles está creciendo constantemente: En nuestro caso, nos interesesa el generador de código typescript-angular
.
Necesitas el generador compilado: swagger-codegen-cli.jar
.
Para descargar la última versión: swagger-codegen-cli
Para más informacion: github.com/swagger-api/swagger-codegen
En este artículo, usaremos el siguiente api, que tiene su propio swagger: api.angular.schule
Lo puedes explorar via Swagger UI
Swagger-codegen dispone de muchos argumentos, los mínimos son:
Tan solo necesitas indicar el jar de codegen-cli, la url donde se aloja swagger.json, indicar que el tipo de generado es typescript-angular y la ruta destino es x.
java -jar ./swagger-codegen-cli-2.4.1.jar generate -i https://api.angular.schule/swagger.json -l typescript-angular -o ./angular_api_client
Con esto ya tendrás una nueva carpeta angular_api_client
con todos los modelos del api, junto a sus servicios.
npm init
npm install
npm run build
npm publish dist --access=public
npm install nombreDelNpmInit --save