Ahora vamos a iniciar con nuestro Kanban, así que manos a la obra 👍
Prerrequisitos
Los prerrequisitos serán de un nivel de conocimientos técnicos y de una instalación mínima requerida en tu máquina.
En conocimientos técnicos es importante que conozcas lo básico de Angular, como son los componentes, directivas, módulos, servicios, routing y formularios.
En la instalación mínima requerida, te preguntarás que debo tener instalado?
Es importante tener instalado node.js y npm para que podamos instalar Angular.
Necesitamos un Editor de texto o IDE, puede ser visual studio code o el IDE o el editor de texto de tu preferencia.
Qué es un IDE?
Es un entorno de desarrollo integrado o interactivo, que te permite hacer más funcionalidades, puedes instalar extensiones en el para ayudarte a ser más productivo en tu trabajo. Incluso hay extensiones para editar el tema de nuestro entorno de desarrollo.
Por último necesitamos el CLI-Angular, que nos facilita la creación, generación, ejecución, testing, deploy, en el caso de no tener aún este último instalado, solo necesitas ejecutar en tu terminal, este comando:
npminstall-g@angular/cli
Un Kanban es como un tablero. Sabemos que hemos estado en una situación social de pandemia, que nos ha afectado un poco a nivel productivo, este proyecto te ayudará a organizar un poco tus tareas del día a día, de una forma ágil y efectiva.
Dirás que este tablero, es parecido a una aplicación de las que encuentras por ahí para descargarla en tu máquina, para qué este proyecto?. Te cuento que con este proyecto podrás ver un poco que tanto tiene esa aplicación que descargas, lo harás tú mismo y podrás personalizarlo de acuerdo a tus necesidades e integrarlo con las herramientas que desees, puedes ir gradualmente aumentandole la complejidad.
En este tablero podremos ir creando nuestras tareas, tendremos 3 listas, una de tareas: ¿qué hacer?, otra de tareas en progreso, y una de está hecho, en estas listas iremos colocando las tareas que vayan pasando por cada una de esas etapas.
Tendremos un home, en él podremos ver una lista de nuestras tareas de acuerdo a la prioridad, estas tareas se crearán en la sección tablero donde crearemos nuestras tareas, las podremos mover por nuestras listas y una vez creadas, podemos, en el home revisarlas por categorías.
Usaremos un poco de componentes CDK, así que repasaremos un poco estos conceptos.
1. Creando nuestro proyecto
Una vez instalado el Angular CLI, crearemos un proyecto y añadiremos Angular Material e incluiremos uno de los temas sugeridos, crearemos el módulo donde iremos añadiendo nuestros componentes de Material y CDK.
En nuestro IDE en la terminal ejecutaremos el siguiente comando:
ngnewworkshop
Podemos seleccionar que sea estricto.
Usaremos SASS, en este caso escogeremos la sintaxis de sasy.
Diremos "yes" al routing
Una vez termina la creación de nuestro proyecto, podemos ejecutarlo con:
ngserve-o
Es hora de incluir Angular Material y los componentes de CDK, ejecutaremos el siguiente comando:
ngadd@angular/material
Seleccionamos un tema:
Configuramos la tipografia global y el browser animation de material.
En nuestro archivo styles.scss incluiremos los estilos del tema que acabamos de incluir
Importamos el módulo de material-cdk en nuestro shared, ya que más adelante incluiremos varios módulos que usaremos en nuestro modulo shared. Ademas crearemos una constante donde pondremos ahi los elementos que vamos a incluir en las declaraciones y en los exports:
En el Modulo de Material-CDK incluiremos dos componentes de Material y creamos una constante para colocar los componentes en las declaraciones y exportarlos para ser usados. Importamos el módulo Toolbar y el icon en nuestro módulo material-cdk.
Coloquemos algo de contenido en nuestro componente Footer y Header.
En nuestro header crearemos un Toolbar, incluiremos algunos iconos para que nuestro toolbar se vea más bonito, si deseas añadir otro icono diferente puedes buscar la lista de iconos en la página de Angular Material.
Nuestro toolbar tendrá un menú, en los anchor añadiremos las rutas que tendremos de los módulos que crearemos en clases posteriores.
Colocaremos un poco de formato, para que nuestra aplicación vaya luciendo mejor.
En el app-routing.module incluiremos el enrutamiento para los modulos respectivos, para que al darle clic al link del 'header' redireccione a la sección respectiva.
Al dar clic sobre los links del header aún no pasa la redirección, es porque en el "Shared" modulo debemos agregar el "RouterModule", para que funcionen las directivas en el header.
Al haver clic en cada link, ahora si ocurre la redirección. Al crear nuestro header colocamos el icnono fuera de los anchore, vamos a inclurilos en el anchore y a colocar un pequeño estilo para el link activo.
Nuestro Board tiene tres columnas, que es donde se mostraran las tareas respectivas, necesitamos un componente de presentacion para la lista y otro para las tareas.
Primero crearemos nuestro componente lista, en una carpeta de componentes dentro de nuestro Board Module
nggcboard/components/list
Incluimos nuestro componente list en el contanedor del board.
En nuestra aplicación usaremos data mockeada, entonces crearemos un servicio mock y lo incluiremos en nuestro contenedor para pasar la data a los componentes de presentación.
Para la data mockeada podemos crear un json o crear un API mock en mocky.io para luego consumirla en tu servicio.
Creamos un servicio en el Core module.
nggscore/services/api
Creamos un index para exportar los servicios que crearemos en el core.
En los imports incluimos el HttpClientModule en el 'app.module.ts'
Importamos unas dependencias en nuestro nuevo archivo llamado api.service.ts, y añadimos en el constructor lo siguiente: http: HttpClient , haciendo uso de la inyección de dependencias (Patron de diseño). Este http, va a ser uso de lo que tenga el HttpClient, que este hace parte del nuevo modulo que incluimos en el app.module.ts
Tambien crearemos dos funciones una para obtener la url del API y otra para el manejo de errores, haciendo uso de HttpErrorResponse.
Para la url del api, he creado 3, para jugar con los diferentes mocks.
//api with one task private apiRoot: string ='https://run.mocky.io/v3/26045374-863c-469d-85c4-51ea1135ce8a';//api without any task private apiRoot: string ='https://run.mocky.io/v3/7841d1af-e8d5-446a-bac5-3506fdd05659';// api with many task private apiRoot: string ='https://run.mocky.io/v3/0933ddef-c9bf-4f26-8ddf-77990fb490cb';
Incluso cree un archivo json con alguna data mockeada.
Si jugamos con los diferentes mocks, vamos a ver como podemos tener mas tareas.
Componente Drag&Drop
Vamos a añadir el componente del Drag&Drop del CDK para mover las tareas, en las diferentes listas. Incluimos el componente en el material-cdk.module.ts
En nuestra página podremos arrastrar nuestra tarea.
Crear Tarea
De tener más tareas en cada lista, podriamos moverlas por los diferentes estados en los que tendríamos nuestras tareas, ahora creemos un componente que nos permita crear las tareas. Con el angular-cli creemos nuestro componente.
ng g c board/components/create-task
Crearemos un formulario reactivo, para ello necesitamos incluir en el board.module.ts el modelo de formulario.
Si colocamos nuestro nuevo componente en el board.component, tal vez nuestra aplicación no se verá muy linda.
...
<app-create-task></app-create-task>
...
Mientras ubicamos el formulario en el lugar correspondiente, crearemos una tributo que guardará la prioridad seleccionado de nuestro dropdown y otro para los valores que mostrará el dropdown.
La idea es que al presionar un boton para crear la tarea podamos en un overlay ver nuestro formulario, para hacer uso del Overlay, vamos a importar en material-cdk el elemento, ademas de importar un componente que usaremos en el formulario de creación para que Textarea de nuestro formulario tengo un crecimiento dinámico.
En el componente incluiremos unas dependencias que necesitamos para este comportamiento del CDK en el texarea.
import { Component, NgZone, OnInit, ViewChild } from '@angular/core';
...
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
import { take } from 'rxjs/operators';
...
@Component({
selector: 'app-create-task',
templateUrl: './create-task.component.html',
styleUrls: ['./create-task.component.scss'],
})
export class CreateTaskComponent implements OnInit {
@ViewChild('autosize') autosize: CdkTextareaAutosize;
...
constructor(private fb: FormBuilder, private _ngZone: NgZone) {}
...
triggerResize() {
// Wait for changes to be applied, then trigger textarea resize.
this._ngZone.onStable
.pipe(take(1))
.subscribe(() => this.autosize.resizeToFitContent(true));
}
Para el overlay vamos a importar el CdkConnectedOverlay, el cual usaremos en las opciones que crearemos para darle una configuración minima a nuestro overlay. Esta lógica la pondremos en el board component.
Al presionar el botón de crear, podemos llamar a nuestro overlay, pero no podemos cerrar en el caso de que no deseemos seguir con este proceso, para ello necesitamos añadir en nuestro componente de creación un boton para cerrar nuestro overlay.
Revisando en nuestra aplicación cuando llamemos al overlay, podemos ver en el formulario un icono que al ser presionado, nos permitirá cerrar nuestro overlay.
Editar Tarea
Hemos creado la funcionalidad para crear tareas, ahora crearemos la funcionalidad para editar una tarea.
En el componente tarea, crearemos un icono que al ser presionado nos permitirá editar nuestra tarea.
Queremos que al presionar el icono podamos editar nuestra tarea emitiendo el evento al padre, y queremos llamar al overlay mostrando la información de la tarea seleccionada. Ya emitimos la data desde el hijo tarea, ahora necesitamos desde el hijo lista, emitirla al padre, para que este pase la data al componente de crear.
En el padre en el componente board, vamos crear un atributo task que será el que pasaremos al create-task y el cual editaremos dentro de la funcion que muestra el overlay.
En el componente create-task vamos a editar el componente para recibir la informacion de la tarea, ademas crearemos un atributo que manera el texto del titulo y del boton, dependiendo si se esta editando o creando.
Para hacer más visible al usuario que hemos eliminado nuestra tarea, vamos a usar el Dialogo de Material para mostrar un mensaje, primero importaremos el componente en el modulo material-cdk.module.ts
Crearemos un componente modal, podemos crearlo solo visible para el modulo del Board, pero pensemos en algo más global y lo crearemos en el Shared, para usarlo en diferentes lugares si lo necesitamos.
ng g c shared/components/modal
En nuestro componente modal, vamos a usar el Portal para cambiar el mensaje que mostraremos en nuestro modal, vamo a importar en el material-cdk.module.ts el Portal
Ahora tenemos con la data mockeada un CRUD, para crear, editar y eliminar nuestra tarea.
4. Modificando el Home
En nuestro Modulo de Home queremos que aparezca una especie de resumén de las tareas que estamos creando, vamos a poner un poco de html en la vista para mostrar las tareas que tenemos creadas.
<main class="home">
<h1 class="home__title">Kanban</h1>
<p>En nuestro día a día siempre tenemos que realizar diferentes tipos de tareas y priorizarlas y dejarlas por escrito nos ayuda a abordarlas mucho más rápido y lograr su realización</p>
<p>Por ello en este tablero te invitamos a organizar tu día a día</p>
<section>
<h2>Lista de tareas</h2>
<div class="home__task-list">
<mat-task class="home__task-list__task" *ngFor="let task of taskList">
<mat-task-header>
<mat-task-title>
<div class="task__task-list__icon">
<mat-icon aria-hidden="false" aria-label="urgent icon" *ngIf="task.priority === 'urgent'">alarm</mat-icon>
<mat-icon aria-hidden="false" aria-label="medium icon" *ngIf="task.priority === 'moderate'">autorenew</mat-icon>
<mat-icon aria-hidden="false" aria-label="medium icon" *ngIf="task.priority === 'low'">assignment_returned</mat-icon>
<span>Tarea</span>
</div>
</mat-task-title>
<mat-task-subtitle class="task__task-list__subtitle"><strong>Fecha de finalización:</strong> {{ task.date | date }}</mat-task-subtitle>
</mat-task-header>
<mat-task-content>
<p class="task__task-list__description">
{{task.description}}
</p>
</mat-task-content>
</mat-task>
</div>
</section>
</main>
En el home.component.ts vamos a consumir la api, para mostrar las tareas en nuestro componente y usaremos la data mockeada.
Nuestro home no se ve muy bonito ni funcional, colocaremos 3 botones, para que al dar clic a algunos de ellos podamos ver las tareas por cada una de las prioridades, editaremos un poco la vista y la lógica del componente.
<main class="home">
<h1 class="home__title">Kanban</h1>
<p>
En nuestro día a día siempre tenemos que realizar diferentes tipos de tareas
y priorizarlas y dejarlas por escrito nos ayuda a abordarlas mucho más
rápido y lograr su realización
</p>
<p>Por ello en este tablero te invitamos a organizar tu día a día</p>
<section class="home__task-list">
<h2 class="home__task-list__title">Lista de tareas</h2>
<div class="home__task-list__buttons">
<button
class="option-button"
mat-button
mat-raised-button
(click)="getPrioritiesTask('urgent')"
>
<mat-icon aria-hidden="false" aria-label="urgent icon">alarm</mat-icon
>Urgentes
</button>
<button
class="option-button"
mat-button
mat-raised-button
(click)="getPrioritiesTask('moderate')"
>
<mat-icon aria-hidden="false" aria-label="medium icon"
>autorenew</mat-icon
>Moderadas
</button>
<button
class="option-button"
mat-button
mat-raised-button
(click)="getPrioritiesTask('low')"
>
<mat-icon aria-hidden="false" aria-label="medium icon"
>assignment_returned</mat-icon
>Bajas
</button>
</div>
<div class="home__task-list__container">
<mat-task class="task" *ngFor="let task of taskList">
<mat-task-header
[ngClass]="{
'task__header task__header--urgent': task.priority === 'urgent',
'task__header task__header--moderate': task.priority === 'moderate',
'task__header task__header--low': task.priority === 'low'
}"
>
<mat-task-title class="task-title">
<div class="task-title__icon">
<mat-icon
aria-hidden="false"
aria-label="urgent icon"
*ngIf="task.priority === 'urgent'"
>alarm</mat-icon
>
<mat-icon
aria-hidden="false"
aria-label="medium icon"
*ngIf="task.priority === 'moderate'"
>autorenew</mat-icon
>
<mat-icon
aria-hidden="false"
aria-label="medium icon"
*ngIf="task.priority === 'low'"
>assignment_returned</mat-icon
>
<span>Tarea</span>
</div>
</mat-task-title>
<mat-task-subtitle class="task__task-list__subtitle"
><strong>Fecha de finalización:</strong>
{{ task.date | date }}</mat-task-subtitle
>
</mat-task-header>
<mat-task-content class="task__content">
<p class="task__task-list__description">
{{ task.description }}
</p>
</mat-task-content>
</mat-task>
</div>
</section>
</main>
Crearemos un servicio para las tareas, primero con el angular-cli crearemos un servicio, haremos uso del BehaviorSubject para manejar el flujo de nuestras tareas.
ng g s core/services/task
En el servicio usaremos el BehaviorSubject, crearemos varias funciones para manejar el crear, editar y eliminar. Ademas crearemos algunos atributos para los observables y para manejar el flujo de la tarea. Tambien incluiremos el servicio Api, para cargar una data inicial.
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService, TaskSchema, ListSchema } from '../';
@Injectable({
providedIn: 'root',
})
export class TaskService {
private readonly boardList = new BehaviorSubject<ListSchema[]>([]);
readonly list$ = this.boardList.asObservable();
readonly getBoardList$ = this.list$.pipe(map((list) => list));
constructor(private apiService: ApiService) {
this.loadInitialData();
}
/* Load initial data to render in a component */
loadInitialData(): any {
return this.apiService.getApi().subscribe((response: any) => {
if (!!response) {
this.boardList.next(response['list']);
}
});
}
/* getter list of Board */
get list(): ListSchema[] {
return this.boardList.getValue();
}
/* setter list of Board */
set list(value: ListSchema[]) {
this.boardList.next(value);
}
/* Add new card to board list */
addTask(data: TaskSchema): void {
const card = data;
const elementsIndex = this.list.findIndex(
(element) => element.id === '1'
);
this.list[elementsIndex].tasks.push(card);
}
/* Edit card on list */
updateTask(data: TaskSchema, listId: string): void {
if (data) {
const elementsIndex = this.list.findIndex(
(element) => element.id === listId
);
const task = this.list[elementsIndex].tasks.map((element) => {
if (element.id === data.id) {
element.date = new Date(data.date);
element.description = data.description;
element.priority = data.priority;
}
return element;
});
}
}
/* Remove a card of board list */
removeTask(dataId: string, list: ListSchema): void {
const elementsIndex = this.list.findIndex(
(element) => element.id == list.id
);
const tasks = this.list[elementsIndex].tasks.filter(
(task) => task.id !== dataId
);
this.list[elementsIndex].tasks = tasks;
}
}
En nuestro Board component crearemos una función que obtendra la data desde el servicio de tarea e importaremos el nuevo servicio.
En el componente para crear la tarea (create-task.component.ts) vamos a hacer el llamado a nuestra tarea de servicio, ademas vamos a incluir una función que crearemos en la carpeta utils, en el shared
En el componente tarea es donde incluimos el boton como icono para eliminar, por lo que tambien necesitamos llamar a nuestro servicio, para llamar a la funcionalidad de eliminar. En nuestro componente de tarea incluiremos el servicio.
En el api de servicios usaremos el mock sin tareas, para usarlo con la tarea de los servicios
export class ApiService {
//api with one task
//private apiRoot: string = 'https://run.mocky.io/v3/26045374-863c-469d-85c4-51ea1135ce8a';
//api without any task
private apiRoot: string = 'https://run.mocky.io/v3/7841d1af-e8d5-446a-bac5-3506fdd05659';
// api with many task
//private apiRoot: string = 'https://run.mocky.io/v3/0933ddef-c9bf-4f26-8ddf-77990fb490cb';
}
En el home component tambien incluiremos nuestro nuevo servicio, para traer las tareas que hemos creado en nuestro kanban.
Es muy importante poder hacer el despliegue de nuestra aplicación, creamos muchas demos, pero no siempre las desplegamos, y aveces no contamos con un dominio, o un hosting. Con github ahora podemos desplegar nuestra aplicación con github actions y tener un dominio y hosting de nuestros archivos con github pages.
GitHub Pages o Páginas de GitHub
Es un servicio de alojamiento de nuestros proyectos; podemos alojar nuestros archivos HTML, CSS y Javascript. Cuando creamos nuestra aplicación en un framework como angular estos ficheros los generamos ejecutando el siguiente comando:
ng build
En algunas ocasiones podemos obtener algún error generando nuestra carpeta dist, que es la que contendra todo nuestro proyecto, usando el siguiente comando podemos solucionarlo o solo instalando de nuevo el npm o yarn.
npm i --only=dev
Al generar nuestra carpeta dist, el contenido que se genera es el que podemos publicar en nuestro GitHub pages.
GitHub Actions o Acciones de GitHub
Nos permite automatizar, personalizar y ejecutar el flujo de trabajo de nuestro proyecto para subirlo en un repositorio con GitHub actions.
Como despliego mi aplicación?
Teniendo nuestra aplicación creada, y en nuestro repositorio de github, vamos a crear una rama con el nombre: gh-pages. Esta rama es la que configuramos para subir nuestros archivos de HTML, CSS y Javascript, que es la aplicación que quedará publicada.
Podemos crear nuestra rama directamente desde la página de GitHub o podemos desde nuestra terminal crear nuestra rama. La idea de esta rama es que este vacia, entonces es necesario limpiarla, para eso odemos hacer lo siguiente si hemos creado nuestro repo desde la web:
La dirección base tendrá el nombre de nuestro repositorio--base-href=/<repository>/" .
Vamos a guardar nuestro cambios, hacemos un commit y un push a nuestro repositorio main
git commit -m "deploy script"
git push origin main
Estamos a pocos pasos de nuestro despliegue.
Si vamos a la pestaña de GitHub action en nuestro repositorio de GitHub, podemos ver varias plantillas para hacer el despliegue. Es bueno usar las opciones, pero vamos a crear nuestra plantilla manual.
Primero vamos a crear en nuestro proyecto, en la raiz una carpeta llamada: ".github".
Dentro de la carpeta ".github" vamos a crear otra carpeta llamada: "workflows"
Dentro de la carpeta crearemos un archivo llamada build-deploy.yml
En el archivo es donde hacemos la configuración, le decimos a GitHub que pasos debe seguir para hacer el despliegue de nuestra aplicación.
En este archivo en el on: en branches debemos colocar el nombre de nuestra rama por defecto: main
En los jobs, en la parte del name para el branch debe ir gh-pages y en el folder se coloca el nombre de la carpeta que se genera dentro de la carpeta dist, en este caso sera: workshop
Guardamos nuestro archivo, hacemos un commit y un push
La URL sigue la siguiente estructura: https://<user>.github.io/<repository>.
Recuerda cada vez que hagas cambios en tu proyecto, haces commit y push a 'main' o tu rama por defecto, la que hayas configurado en el 'on', para reflejar los cambios en nuestra página.