--- title: Taller Angular description: Interacción entre componentes --- ## Conceptos Angular ### Arquitectura El esquema de la arquitectura del Angular son los [módulos](https://angular.io/guide/architecture-modules), [componentes](https://angular.io/guide/architecture-components#component-metadata), [plantillas](https://angular.io/guide/architecture-components#templates-and-views), [metadatos](https://angular.io/guide/architecture-components#component-metadata), [mapeo de datos](https://angular.io/guide/architecture-components#data-binding), [directivas](https://angular.io/guide/architecture-components#directives), [servicios](https://angular.io/guide/architecture-services) y la [inyección de dependencia](https://angular.io/guide/architecture-services). * Esquema de la arquitectura ![Arquitecuta](https://angular.io/generated/images/guide/architecture/overview2.png) Figura: Esquema de la arquitectura del Angular --- #### Esquema de la arquitectura ![](https://i.imgur.com/3JTA06H.png) --- ### Modulos En Angular se aplica el enfoque modular en la contrucción de la aplicación Web. Angular cuenta con su propio sistema de modulos llamado `@NgModule`. Ejemplo : ```javascript= // Importación de clase a utilizar import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { MessageService, ConfirmationService } from "primeng/api"; import { CustomFilterComponent } from "./custom-filter/custom-filter.component"; import { CustomModalComponent } from "./custom-modal/custom-modal.component"; import { CustomToastComponent } from "./custom-toast/custom-toast.component"; import { CustomSpinnerLoaderComponent } from "./loading/custom-spinner-loader.component"; //Decorador de Angular @NgModule( //Metadatos asociados al módulo { declarations: [ CustomSpinnerLoaderComponent, CustomToastComponent, CustomModalComponent, CustomFilterComponent, ], imports: [ NgbModule, CommonModule, FormsModule, ReactiveFormsModule, ], exports: [ CustomSpinnerLoaderComponent, CustomToastComponent, CustomModalComponent, CommonModule, ], providers: [ MessageService, ConfirmationService ] } ) export class CustomCommonModule { } ``` El decorador `@NgModule()` define una función que permite especificar **metadatos**, estos agregan comportamientos adicionales para el módulo. Los metadastos más utilizados son : * **declarations**: Listado de componentes, directivas y pipes que son parte del módulo. * **exports**: Listado de componentes, directivas y pipes que serán visible para su utilización en otros componentes, plantillas o módulos. * **imports**: Listado de módulos que son requeridos para el módulo. > En cada módulo se define un listado de componentes que pueden ser > utilizados por otros módulos. --- ### Servicios Son conjuntos de código y lógica que se ejecutan en forma asíncrona y provee datos a los componentes. Ejemplo : ```javascript= // Importación de clase a utilizar import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { GlobalService } from '../global-service/global.service'; import { Poblacion } from '../model/poblaciones'; import { ServiceResponse } from '../model/response'; import { ServicioPersonalizadoBase } from '../util/servicio-base-personalizado'; import { MENU_URLS } from 'src/app/util/routes'; //Decorador de Angular @Injectable( //Metadatos asociados al servicio { providedIn: 'root' } ) export class PoblacionesService extends ServicioPersonalizadoBase<ServiceResponse<Poblacion>>{ poblacionInstance: Poblacion | null = null; constructor( private http: HttpClient, private globalSvc: GlobalService, ) { super(MENU_URLS.POBLACIONES.SERVICE_URL); this.httpClient = http; this.globalServices = globalSvc; } } ``` * El decorador `@Injectable` es importado desde el `@angular/core` * El metadado `providedIn: 'root'`: indica al Angular que el servicio estará disponible en forma global para toda la aplicación :::info Por defecto al crear un servicio utilizando el Angular CLI se agrega el metadato `providedIn: 'root'` lo que significa que el servicio se instancia una vez y se comparte a nivel de aplicación. ::: --- ### Componentes Los componentes son bloques de código que gestionan la interacción de las vistas con las aplicaciones en Angular. Ejemplo : ```javascript= // Importación de clase a utilizar import { AfterViewInit, Component, Inject, OnInit, Optional, ViewChild, Input } from '@angular/core'; import { CustomFilterName } from 'src/app/model/custom-filter-name'; import { FilterParam } from 'src/app/model/filter-params'; import { CustomModalService } from 'src/app/services/custom-modal.service'; import { PoblacionesService } from 'src/app/services/poblaciones.service'; import { TituloService } from 'src/app/services/titulo.service'; import { TablaPersonalizadaAbstract } from 'src/app/util/tabla-personalizada-abstract'; import { DataTablePesonalizadoColumn } from 'src/app/model/data-table-personalizado-column'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { MODAL_DIALOG_DATA } from 'src/app/model/custom-modal-options'; import { MODAL_OPERATION } from 'src/app/model/modal-data'; import { PrimeTablePersonalizadoComponent } from 'src/app/components/prime-table-personalizado/prime-table-personalizado.component'; import { Output } from '@angular/core'; import { EventEmitter } from '@angular/core'; import { ProgramasService } from '../../services/programas.service'; //Decorador de Angular @Component( //Metadatos asociados al componente { selector: 'app-poblaciones-list', templateUrl: './poblaciones-list.component.html', styles: [ ] } ) export class PoblacionesListComponent extends TablaPersonalizadaAbstract implements OnInit, AfterViewInit { @ViewChild(PrimeTablePersonalizadoComponent) referenciaTabla: PrimeTablePersonalizadoComponent | null = null; @Input() mostrarTitulo: boolean = true; @Input() requierePrograma: boolean = true; @Input() poblacion: any; @Output() selectedItemChange: EventEmitter<any> = new EventEmitter(); fnServiceCallBack: any; modalOperation = MODAL_OPERATION; fnListadoCallBack: any; constructor( private serviceInstance: PoblacionesService, private modalServiceInstance: CustomModalService, private tituloServiceInstance: TituloService, @Optional() public modalInstance: NgbActiveModal, @Optional() @Inject(MODAL_DIALOG_DATA) public dialogData: any, private programasService: ProgramasService, ) { super(); this.entityService = serviceInstance; this.modalService = modalServiceInstance; this.tituloService = tituloServiceInstance; // function to be executed to get data from service this.fnServiceCallBack = (params: FilterParam) => { return this.serviceInstance.obtenerListadoFiltradoEnJson(params); }; } // Método del ciclo de vida ngOnInit(): void { } // Método del ciclo de vida ngAfterViewInit() { } // Método utilizado para crear un modal y el contenido a mostrar // dentro de la misma es un componente abirFormulario(element?: any) { } // Método utilizado para eliminar un registro seleccionado eliminar(element: any) { } // Método utilizado para definir el conjunto de atributos a // mostar en la grilla obtenerDefinicionTabla(): Array<DataTablePesonalizadoColumn> { return []; } // Método utilizado para definir el conjunto de filtro // aplicable a la grilla obtenerDefinicionFiltro(): Array<CustomFilterName> { return []; } //Método para captura el evento emitido por el componente de prime table personalizado // el mismo captura el item seleccionado y lo vuelve a pasar al componente padre capturarOperacionSeleccion(datos: any): void { this.selectedItemChange.next(datos); } } ``` * El decorador `Component` es importado desde el `@angular/core` * `seletor`: Es el selector CSS (identificador de componente), utilizado por el Angular para crear e insertar una instancia del componente donde es utilizado. Por ejemplo, si el componente es utilizado por otro [Módulo](https://angular.io/guide/architecture-modules) dentro de la plantilla HTML `<app-poblaciones-list></app-poblaciones-list>`, entonces Angular inserta una instancia del componente `PoblacionesListComponent` en la posición del tags. * `templateUrl`: Es la referencia relativa de un archivo de plantilla en HTML que será utilizada por el componente * `stylesUrl`: Es la referencia relativa de un archivo de estilos en CSS que será utilizada por el componente --- ### Plantillas La plantilla es una combinación de HTML con tags de Angular que modifican los elementos HTML antes de ser mostrado en la pantalla. Ejemplo : ```htmlembedded= <app-custom-spinner-loader></app-custom-spinner-loader> <div class="card"> <div class="card-body"> <div class="modal-header" *ngIf="mostrarTitulo"> <h4>{{ titulo }}</h4> </div> <app-prime-table-personalizado [ocultarSelector]="requierePrograma" tipoSelector="radio" [fnCallBack]="fnServiceCallBack" [nombreColumnas]="obtenerDefinicionTabla()" [nombreFiltros]="obtenerDefinicionFiltro()" (accionChange)="abirFormulario()" (filaChange)="capturarOperacion($event)" (selectedChange)="capturarOperacionSeleccion($event)" > </app-prime-table-personalizado> </div> </div> ``` * Este ejemplo utiliza tags propios del HTML como : * [div](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div) * Así también incluye tags conocidos por el Angular como : * `<app-custom-spinner-loader>` * `[ocultarSelector]` * `(accionChange)` * `<app-prime-table-personalizado>` * `*ngIf` --- ## Componente de listado de Población ![](https://i.imgur.com/GjH45go.png) El componente de listado consta de dos archivos : * poblaciones-list.component.ts * poblaciones-list.component.html El componente de listado permite las siguientes operaciones : 1. Opción de ***agregar***, ***filtar***, ***exportar en pdf*** y ***excel*** 2. Opción de ordenamiento por campos definidos en la grilla 3. Opción de ***editar*** y ***eliminar*** por fila de la grilla 4. Opción de ***páginación*** ### Diagrama de Clase ![](https://i.imgur.com/a5IGCdS.png) ### Relación entre componentes ![](https://i.imgur.com/fUdF2Zx.png) --- ### Secuencia de la agregación ![](https://i.imgur.com/gmp6aVR.png) * La interacción comienza cuando el usuario oprime clic en el botón de **Agregar+** 1. El evento invoca al método **llamadaFormulario()**, que emite un evento vía el objeto ***EventEmiter*** *(accionChange)* 2. El método *llamadaFormulario* desencadena un evento, el cual es capturado por el componente `PoblacionesListComponent`. 3. El método ***abrirFormulario***, es gestionado en el componente `PoblacionesListComponent` y permite la generación del formulario utilizando un diálogo. 4. Al cerrar el formulario de Población el mismo emite un evento que es capturado por el componente `PoblacionesListComponent`. 5. El evento de ***closeEdit*** es gestionado por el componente `PoblacionesListComponent`, el cual ejecuta un método definido en el componente de `PrimeTablePersonalizadoComponent` con la finalidad de actualizar los valores de la tabla. --- ### Secuencia de ordenación ![](https://i.imgur.com/Wnxh44T.png) * La interacción comienza cuando el usuario oprime clic sobre la columna por el cual desea ordenar. 1. El evento invoca al método de **sort()**, que permite configurar los parámetros de ordenamiento en base a la columna seleccionada. 2. Realiza la llamada interna para la obtención de datos desde el ***servicio***. > En este punto se utiliza la función definida en el componente de `PoblacionesListComponent` ***(fnCallBack)*** > ` this.fnServiceCallBack = (params: FilterParam) => { return this.serviceInstance.obtenerListadoFiltradoEnJson(params); };` 3. Se invoca al método de **obtenerListadoFiltradoJson()** del servicio `PoblacionesService` 4. Se invoca al método get del servicio ***HttpClient***, el cual se encarga de obtener los registros del web services de poblaciones. 5. El backend retorna los datos resultantes al servicio del frontend. 6. El Servicio vía el objeto ***Observable*** retorna los registros resultantes al componente. 7. El componente de `PrimeTablePersonalizado` gestiona la renderización de los datos recibidos. 8. El usuario final visualiza los datos actualizados. --- ### Secuencia de edición ![](https://i.imgur.com/nNvnNOm.png) * La interacción comienza cuando el usuario oprime clic en el icono de **Editar** de la fila de la tabla. 1. El evento invoca al método **operacionFila()**, que emite un evento vía el objeto ***EventEmiter*** *(filaChange)* 2. El método *operacionFila* desencadena un evento, el cual es capturado por el componente `PoblacionesListComponent`. 3. El método ***capturarOperacion***, es gestionado en el componente`PoblacionesListComponent` y permite la generación del formulario utilizando un diálogo. 4. Al cerrar el formulario de Población el mismo emite un evento que es capturado por el componente `PoblacionesListComponent`. 5. El evento de ***closeEdit*** es gestionado por el componente `PoblacionesListComponent`, el cual ejecuta un método definido en el componente de `PrimeTablePersonalizadoComponent` con la finalidad de actualizar los valores de la tabla. --- ### Secuencia de eliminación ![](https://i.imgur.com/Fs0wnJf.png) * La interacción comienza cuando el usuario oprime clic en el icono de **Eliminar** de la fila de la tabla. 1. El evento invoca al método **operacionFila()**, que emite un evento vía el objeto ***EventEmiter*** *(filaChange)* 2. El método *operacionFila* desencadena un evento, el cual es capturado por el componente `PoblacionesListComponent`. 3. El método ***capturarOperacion***, es gestionado en el componente`PoblacionesListComponent` y permite la generación del dialogo de confirmación de la operación de eliminación. 4. Al cerrar el dialogo de confirmación el mismo emite un evento que es capturado por el componente `PoblacionesListComponent`. 5. El evento de ***actualizarComponenteTabla*** es gestionado por el componente `PoblacionesListComponent`, el cual ejecuta un método definido en el componente de `PrimeTablePersonalizadoComponent` con la finalidad de actualizar los valores de la tabla. --- ### Secuencia de paginación ![](https://i.imgur.com/nJgm5dA.png) 1. El evento invoca al método de **page()**, que permite configurar los parámetros de paginación en base a los datos de la página seleccionada. 2. Realiza la llamada interna para la obtención de datos desde el ***servicio***. > En este punto se utiliza la función definida en el componente de `PoblacionesListComponent` ***(fnCallBack)*** > ` this.fnServiceCallBack = (params: FilterParam) => { return this.serviceInstance.obtenerListadoFiltradoEnJson(params); };` 3. Se invoca al método de **obtenerListadoFiltradoJson()** del servicio `PoblacionesService` 4. Se invoca al método get del servicio ***HttpClient***, el cual se encarga de obtener los registros del web services de poblaciones. 5. El backend retorna los datos resultantes al servicio del frontend. 6. El Servicio vía el objeto ***Observable*** retorna los registros resultantes al componente. 7. El componente de `PrimeTablePersonalizado` gestiona la renderización de los datos recibidos. 8. El usuario final visualiza los datos actualizados. --- ## Componente de formulario de Población ![](https://i.imgur.com/brOuFPR.png) El componente de formulario consta de dos operaciones : * GUARDAR * CANCELAR El componente de formulario consta de input tipo text ```htmlembedded= <div class="form-group"> <label for="nombre">Nombre <span class="text-danger"> (*)</span></label> <input id="nombre" type="text" required oninput="this.value = this.value.toUpperCase()" formControlName="nombre" placeholder="Nombre" class="form-control" [class.is-invalid]=" entityForm.controls.nombre.invalid && entityForm.controls.nombre.touched " /> <small class="text-danger" [class.d-none]=" entityForm.controls.nombre.valid || entityForm.controls.nombre.pristine"> Nombre es requerido </small> </div> ``` y selectores que permiten mostrar datos relacionados : ```htmlembedded= <div class="form-group"> <label for="coberturaGeografica">Cobertura Geográfica <span class="text-danger"> (*)</span></label> <ng-select required formControlName="coberturaGeografica" [compareWith]="compareOption"> <ng-option *ngFor="let opt of listCobertura | async" [value]="opt">{{opt.nombre}}</ng-option> </ng-select> <small class="text-danger" [class.d-none]=" entityForm.controls.coberturaGeografica.valid || entityForm.controls.coberturaGeografica.pristine"> Cobertura Geográfica es requerido</small> </div> ``` ### Diagrama de Clase ![](https://i.imgur.com/OcyD1e0.png) --- ### Relación entre componentes ![](https://i.imgur.com/TGZfK3U.png) --- ### Servicios utilizados #### NgModal [NgModal](https://ng-bootstrap.github.io/#/components/modal/api#NgbModal) : > Es el servicio principal para la creación de diálogos de tipo modal, que definen el contenedor para el formulario. > consta de una propiedad principal : **activeInstances** y varios métodos para la interacción con el mismo, el más utilizado es el **open()** > #### Inyección de dependencias > Un componente puede delegar ciertas tareas a los *servicios*, como la obtención de datos del servidor, la validación de la entrada del usuario o el registro directo en la consola. Al definir estas tareas de procesamiento en una clase de **servicio inyectable**, haces que esas tareas estén disponibles para cualquier componente. ##### Ejemplo de inyección de dependencias por tipo de clase > Normalmente al crear un servicio se genera una clase con el decorador ```javascript= @Injectable({ providedIn: 'root' }) ``` > con este decorador el Angular gestiona la creación de la instancia de la clase y la pone a disposición de la aplicación. > La forma de utilizar la inyección de dependencias es a través del constructor de los componentes. ```javascript= export class CustomModalService { constructor(private modalService: NgbModal) { } } ``` ### Secuencia de creación de formulario ![](https://i.imgur.com/XgYAgUq.png) --- ### Secuencia de la operación Guardar ![](https://i.imgur.com/3galZoq.png) --- ### Secuencia de la operación Actualizar ![](https://i.imgur.com/zR839j3.png) --- ### Paso de parámetro entre componentes padre/hijo * Desde un componente padre al hijo 1. Utilizando las propiedades (**@Input**) definidas en el hijo Ejemplo : ```htmlembedded= <ng-template ngbNavContent> <app-identificacion-list [poblacion]="params"></app-identificacion-list> </ng-template> ``` 2. Utilizando el servicio padre Ejemplo : `poblacionInstance: Poblacion | null = null;` > Se define una variable de instancia en el servicio padre donde será gestionado el valor a ser compartido con el hijo 3. Utilizando referencia del componente Ejemplo : ```javascript= @ViewChild(PrimeTablePersonalizadoComponent) referenciaTabla: PrimeTablePersonalizadoComponent | null = null; ``` > Usando el decorador **(@ViewChild)** podemos acceder a la instancia del componente y a través de ella acceder a las propiedades definidas en el componente para pasar los valores necesarios ### Paso de parámetro entre componentes hijo/padre * Desde un componente padre al hijo 1. Utilizando las propiedades (**@Output**) definidas en el hijo Ejemplo : ```htmlembedded= <app-prime-table-personalizado (accionChange)="abirFormulario()" (filaChange)="capturarOperacion($event)" </app-prime-table-personalizado> ``` 2. Utilizando el servicio padre Ejemplo : ```javascript= // Se agrega el item previamente vinculado al programa if(response.respuesta !== null){ this.programasService.problemaItem = response.respuesta.problemas; } ``` ### Paso de parámetro entre componentes - modal Para los dialogos el paso de párametro se realiza vía el servicio de invocación Ejemplo : ```javascript= abrirFormulario(element?: any) { this.modalServiceInstance?.openForm(PoblacionesFormComponent, { //Con estos argumentos es posible //pasar datos al formulario antes de ser construidos // vía <Injectores> element: element, data: { titulo: MENU_URLS.POBLACIONES.TITLE, } }). result.then((operationResult) => { //En este punto es recibido los //parámetros desde el formulario de //población // la variable <operationResult> // contendrá los datos this.referenciaTabla?.filtrar({}); }, reason => { this.referenciaTabla?.filtrar({}); } ); } ``` > En el formulario de **PoblacionFormComponente**, se agrega la dependencia del servicio de *NgbActiveModal* el cual permite la referencia al contenedor (dialogo/modal) activo. ```javascript= export class PoblacionesFormComponent extends CustomAbstractFormComponent implements OnDestroy, OnInit { constructor( public modalInstance: NgbActiveModal, ) { super(); } // Devuelve el control al componente que lo // llamó. LLamando al método de close se // puede pasar los parámetros requeridos al // componente que lo llamó closeEdit(element?: any) { this.modalInstance.close(element); } } ``` ## Componente de detalle de Población ### Listado de identificación ![](https://i.imgur.com/Sxr2L9d.png) El componente de listado consta de la operación : * AGREGAR --- ## Componente de detalle de Población ### Formulario de identificación ![](https://i.imgur.com/MRexsW0.png) El componente de formulario consta de dos operaciones : * GUARDAR/ACTUALIZAR * CANCELAR El componente de formulario consta de input tipo text y selectores que permiten mostrar datos relacionados ## Referencias * https://angular.io/guide/architecture-services * https://www.tektutorialshub.com/angular/injection-token-in-angular/