@jussanjuan/angular-pjsj
v12.2.5
Published
La motivación proncipal de la librearia es la de crear elementos comunes que se presentan en distintos desarrollos para poder distribuirlos desde este repositorio y centralizar su evolución. Con esta libreria vas a poder crear aplicaciones angular de mane
Downloads
21
Keywords
Readme
Angular PJSJ
La motivación proncipal de la librearia es la de crear elementos comunes que se presentan en distintos desarrollos para poder distribuirlos desde este repositorio y centralizar su evolución. Con esta libreria vas a poder crear aplicaciones angular de manera mucho mas ágil, centrado la total atención en el problema a resolver y no al desarrollo de componentes como modales, alertas, tablas, etc.
Getting Started
Install
npm i @jussanjuan/angular-pjsj
import { AngularPjsjModule } from '@jussanjuan/angular-pjsj';
NgModule({
declarations: [
...
],
imports: [
...
AngularPjsjModule,
...
],
Dependencidas necesarias
npm i @angular/flex-layout
npm i @angular/material
npm i @angular/cdk
Configuración de Clase
La configuración de clase de una entidad es el elemento central sobre el cual se crearon los demas desarrollos genéricos. Esta configuración contiene desde el nombre de la entidad hasta el tipo de cada uno de los campos de la entidad. Cada una de las entidades que se quieran mostrar con los componentes debe tener definido el metodo estatico getClassConfig() : ClassConfig que va a retornar su configuración particular
Clases
ClassConfig
| Atributos | Tipo | Observaciones | | :-------- | :--- | :------------ | | entityName | String | Texto estatico que será el nombre con el cual se presentará la entidad. Por ejemplo el entityName de una entidad Person es 'Persona'. | | orderFilter? | string[] | Arreglo de String que contiene el nombre los atributos y el orden en el que se van a mostrar en el selector de atributos a filtrar | | idKeys | string[] | Arreglo que contiene el nombre de los atributos que forman la clave primaria o Id de la entidad | | deleteKeys? | string[] | Arreglo que contiene los atributos que se van a mostrar en un cartel de confirmación de eliminación de un elemento | | fieldConfig | FieldConfigHGeneric[] | Arreglo con toda la configuración de cada uno de los campos de la entidad en cuestion |
FieldConfigHGeneric
| Atributos | Tipo | Observaciones | | :-------- | :--- | :------------ | | key | string | Nombre del campo en la entidad. Por ejemplo si tenemos la clase Person en TypeScript que tiene el atributo de clase name entonces la key debe ser el string 'name' | | display | String | Es el texto con el que se va a presentar ese campo al usuario final. Siguiendo el ejemplo de Persona cuyo campo se llama name entonces un buen valor para display sería el string 'Nombre de Persona' | | isColumn | Boolean | Campo booleano que va a indicar si el campo en cuastion se va a mostrar al usuario o no, en la tabla generica o en otro componente genérico. | | isFilter | Boolean | Campo booleano que va a indicar si ese campo va a ser el valor de un filtro o no. | | typeFilter | FieldType | Este campo es el tipo de filtro que se va a pintar en el formulario de filtros del componente Filtro Genérico. Dependiendo del valor de este atributo se va a pintar un input del tipo texto o númerico o un select, etc | | url? | string | En caso de ser un campo de tipo select el que se va a pintar en un filtro. Se puede usar esta propiedad para indicarle al componente de filtro que debe ir a buscar la información del select de un servicio rest que debe ser un get | | options? | any[] | En caso de ser un campo de tipo select simple se puede usar esta propiedad para indicarle los valores que se van a mostrar. Un ejemplo es el select del sexo de una persona que siempre van a ser dos valores 'Masculino' y 'Femenino'. Otro uso de este atributo es para los datos de tipo booleano que se desean mostrar de una forma especifica en el componente que los muestre. En este caso se van a levantar los dos primeros valores como los representativos del mismo. Siendo el valor en la posición 0 del arreglo para el valor True y el contenido de la posición 1 es como se va a mostrar el valor False del atributo en cuestion. Un ejemplo es si queremos indicar si una persona esta casada o no y tenemos el campo isMarried en la clase y queremos mostrar los valores 'Si' cuando sea True y 'No' cuando sea False, entonces este campo debe contener el arreglo ['Si', 'No']. Adicionalmente en las tablas como en otros componentes se puede enviar HTML para pintar el campo a conveniencia. | optionsCheck? |any[] | Atributo auxiliar para uso exclusivo e interno del filtro genérico |
FieldType
Esta clase es un enumerador y los valores son los siguientes:
| Valores | Observaciones | | :-------- | :------------ | | inputText | Este enumerador va a indicar que el campo es del tipo texto, para trabajarlo como tal en html | | inputNumber | Este enumerador va a indicar que el campo es del tipo número. | | inputPassword | Este enumerador va a indicar que el campo es del tipo privado. | | radioButton | Este enumerador va a indicar que el campo es un radio button. Se va a requerir del atributo ClassConfig.options o url para mostrar sus valores | | textarea | Este enumerador va a indicar que el campo es del tipo texto y que la entrada es de una longitud considerable. | | select | Este enumerador va a indicar que el valor del campo se debe ingresar desde un selector de opciones. Por lo tanto tiene un número finito de opciones y que se tienen que recuperar ya sea desde la ClassConfig.url o de manera estatica de ClassConfig.options. | date | Este enumerador va a indicar que el valor del campo es una fecha y se le debe aplicar filtros para mostrarlas en el formato correcto. El ingreso del valor será un DatePicker. | file | Este enumerador va a indicar qeu el valor de entrada es un archivo. [Aún no esta soportado por los componentes genéricos] | | check | Este enumerador va a indicar que el valor es un dato booleano. |
Ejemplo
Este ejemplo es sobre la clase Hero donde tenemos el nombre del heroe su identificador y superpoderes.
import { ClassConfig, FieldType } from 'projects/angular-pjsj/src/public-api';
import { Superpower } from './superpower';
export class Hero {
id: number;
name: string;
powers?: Superpower[];
public static getClassConfig(): ClassConfig {
return {
entityName: 'Heroes',
idKeys: ['id'],
fieldConfig: [
{
key: 'id',
display: 'Identificador',
isColumn: true,
isFilter: false,
typeFilter: FieldType.inputText
},{
key: 'name',
display: 'Nombre',
isColumn: true,
isFilter: true,
typeFilter: FieldType.inputText
},{
key: 'powers',
display: 'Superpoderes',
isColumn: false,
isFilter: false,
typeFilter: FieldType.select,
url: 'http:localhost:8080/selects/superpowers'
}]
}
}
}
Componentes
Home Genérico (GUI)
Este componente genera una GUI compuesta de un navbar, donde se muestra el título de la aplicación y un sidenav, donde se generan links de acceso a los potenciales recursos, más un link para realizar el logout de la aplicación. La GUI se generará en base a la clase de configuración HomeConfig.
Inputs de Home Genérico
- Configuración de componente: La configuración es una instancia de HomeConfig y es donde se obtiene toda la información necesaria para mostrar y crear la GUI genérica.
Outputs de Home Genérico
- Evento de logout: El componente Home expone la propiedad logoutEvent para emitir el evento click del link de logout (Salir). La propiedad se debe asociar a un método (p.e. 'logoutMethod()') donde se defina la lógica asociada al evento.
Modo de uso:
<g-home [homeConfig]="homeConfig" (logoutEvent)="logoutMethod()"> <!--Cuerpo de la aplicación --> </g-home>
Clases de configuración.
HomeConfig
| Atributos | Tipo | Observaciones | | :-------- | :--- | :------------ | | appName | String | Texto estático que será el nombre de la aplicación que se mostrará en el navbar. | | linksMenu | LinkMenu[] | Arreglo de LinkMenu que contiene el nombre del link, path del recurso y el string del ícono que se mostrará en los links del sidenav. |
LinkMenu
| Atributos | Tipo | Observaciones | | :-------- | :--- | :------------ | | name | String | Nombre que se mostrará en el link. | | path | String | Texto que representa la ruta al recurso que invoca el link. | | icon | String | Texto que representa el ícono que acompaña el link. |
Filtro Genérico
Este componente genera filtros de una entidad según su configuración de clase. El formulario de filtros se generará en base a la configuración de cada campo. Por ejemplo si un campo tiene como typeFilter el tipo TypeFilter.inputText, entonces el filtro para ese campo sera un input del tipo texto y asi. Este componente esta compuesto de la siguiente manera:
(*1)
Búscar ${entityName} por filtro
(*2) (*3) (*4) (*5)
+------------------------+ +------------------------+ +--------------+ +------------------------+
| Seleccionar campo V | | ${Input dinamico} | | Boton Búscar | | Boton Limpiar Filtros |
+------------------------+ +------------------------+ +--------------+ +------------------------+
Referenicias:
- Es el nombre de la entidad recuperada del campo ClassConfig.entityName.
- El seleccionador de campos es un select donde se muestran todos los valores del atributo ClassConfig.fieldConfig.*.display, es decir el campo display de cada uno de los elementos del arreglo fieldConfig.
- En este espacio se va a poner el elemento que corresponda. Ya sea un DatePicker para un campo del tipo fecha, un input texto para otro del tipo string y asi.
- Este boton es el que libera el evento de busqueda e indica al componente que lo use que los valor a filtrar cambiaron, asi tambien envia toda la información del nuevo filtro.
- Elimina todos los valores de los filtros.
Inputs de Filtro Genérico
- Configuración de clase: La configuración de clase es una instancia de ClassConfig y es donde se obtienen toda la información necesaria para mostrarla y crear los inputs genéricos.
Modo de uso:
<g-filter #gFilter [classConfig]="classConfig"></g-filter>
Tabla Generica
Esta tabla esta diseñada para poder adaptarse y presentar cualquier tipo de información. Para poder usar está tabla se debe ingresar los siguientes elementos como "input":
- Instancia del service a usar: Es una instancia de un servicio angular que herede de la superclase GenericService. Este servicio se debe inyectar en el componente que utilice La tabla generica. La tabla generica útiliza una instancia de GenericService de una cierta entidad para traer sus datos.
+------------------------+ +-----------------------------+
| | | |
| Tabla Generica | | GenericService<C,R> |
| | | |
+------------^-----------+ +--------------^--------------+
| |
|Envia instancia |Hereda Funcionalidades
|de ServiceE |
| |
+------------|-----------+ +----------|----------+ +-------------------------+
| | Se Inyecta | | Solicita Datos | |
| Componente que <--------------- ServiceE -------------------| Web Service |
| Usa tabla Generica | | | | |
+------------------------+ +---------------------+ +-------------------------+
- Configuración de clase: La configuración de clase es una instancia de ClassConfig y es donde se obtienen toda la información necesaria para mostrar la información de cada campo de una cierta entidad.
- [Opcional] Filtro: es una instancia del componente de g-filter. De esta manera se va a vincular con la tabla para indicarle cuando se filtre o no un dato y mostrar los elementos filtrados.
- [Opcional] Acciones: Se entiende por acción a las acciones que se desean realizar por cada elemento de la tabla. Un ejemplo puede ser: ver el detalle, editar el elemento, etc. Cada una de las acciones se va a corresponder con un boton en la tabla en la columna de Acciones. El input del componente es un Arreglo de ActionConfig
| Atributo | Observaciónes | | :------- | :------------ | | event | Instancia de @angular/core/EventEmitter que es la que va a tener la función que manejará el click del botón. | | text | Texto estatic que va a ser el contenido del boton | | textResolver? | Función que recibe como parametro el elemento y retorna el texto del boton. Se va a invocar cada vez que cambie el elemento. | | colorResolver? | Función que recibe como parametro el elemento y puede retornar el color del boton. Siendo los colores posibles 'warn', 'accent', 'primary' o 'none'. |
Ejemplos
- app.component.html
<g-home #gHome [homeConfig]="homeConfig" (logoutEvent)="logoutMethod()">
<router-outlet></router-outlet>
</g-home>
- app.component.ts
import { Component, EventEmitter } from '@angular/core';
import { HomeConfig } from 'projects/angular-pjsj/src/lib/domain/generic/home-config';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
homeConfig: HomeConfig = this.getHomeConfig();
getHomeConfig(): HomeConfig {
return {
appName: 'Example App',
linksMenu: [
{
name: 'Heroes',
path: 'heroes',
icon: 'fas fa-address-book'
}, {
name: 'Opción 1',
path: 'opcion-1',
icon: 'fas fa-adjust'
}, {
name: 'Opción 2',
path: 'opcion-2',
icon: 'fas fa-anchor'
}, {
name: 'Opción 3',
path: 'opcion-3',
icon: 'icono-arg-seguridad-red'
}]
}
}
constructor() { }
salir() {
// TODO lógica para evento logout del componente GHome.
console.log("Click en Salir");
}
}
- app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroesComponent } from './heroes/heroes.component';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2/page2.component';
import { Page3Component } from './page3/page3.component';
const routes: Routes = [
{ path: '',
redirectTo: '/heroes',
pathMatch: 'full'
}, {
path: 'heroes',
component: HeroesComponent
}, {
path: 'opcion-1',
component: Page1Component
}, {
path: 'opcion-2',
component: Page2Component
}, {
path: 'opcion-3',
component: Page3Component
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { };
- heroes.component.html
<g-filter #gFilter [classConfig]="classConfig"></g-filter>
<g-table
#tabservice
[gFilter]="gFilter"
[classConfig]="classConfig"
[service]="service"
[actions]="actions">
</g-table>
- heroes.component.ts
import { Component, EventEmitter } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';
import { ClassConfig, ActionConfig, MessageService } from 'projects/angular-pjsj/src/public-api';
import { MatDialog } from '@angular/material/dialog';
import { ConfigSuperpowerComponent } from './config-superpower/config-superpower.component';
@Component({
selector: 'app-heroes',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class HeroesComponent {
classConfig: ClassConfig = Hero.getClassConfig();
actions: ActionConfig[];
constructor(public service: HeroService, private messageSerivce: MessageService, private dialog: MatDialog) {
// Creción del evento de Carteles
let dialogs = new ActionConfig();
dialogs.event = new EventEmitter();
// Se subscribe la funcion que se va a ejecutar al click del boton
dialogs.event.subscribe((hero) => this.detail(hero));
// Se agrega el texto del boton
dialogs.text = 'Carteles';
// Se agrega un color al boton, adicionalmente puede cambiar según los atributos del heroe
dialogs.colorResolver = (hero) => {
return 'accent'
}
let superpowers = new ActionConfig();
superpowers.event = new EventEmitter();
superpowers.event.subscribe((hero) => this.powerConfig(hero));
superpowers.text = 'Super Poderes';
this.actions = [superpowers, dialogs];
}
detail(hero: Hero) {
...
...
}
powerConfig(hero) {
...
...
}
}
- heroes.service.ts
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { HttpClient } from '@angular/common/http';
import { LoadService, MessageService, GenericService } from 'projects/angular-pjsj/src/public-api';
@Injectable({
providedIn: 'root'
})
export class HeroService extends GenericService<Hero,Hero> {
url: string;
constructor(httpClient: HttpClient, loadService: LoadService, messageService: MessageService) {
super(httpClient, loadService, messageService);
this.url = '/heroes';
}
}
Entity Data
Muestra todos los campos de una entidad que tengan en el atributo "column = true" del FieldConfig correspondiente.
Ejemplo de uso:
- hero-detail.component.html
<mat-dialog-content>
<entity-data [classConfig]="heroCC" [entity]="hero"></entity-data>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button [mat-dialog-close]="false">Cerrar</button>
</mat-dialog-actions>
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Hero } from '../hero';
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.sass']
})
export class HeroDetailComponent implements OnInit {
heroCC = Hero.getClassConfig();
hero: Hero;
constructor(
@Inject(MAT_DIALOG_DATA) public data: any
) {
this.hero = data;
}
ngOnInit(): void {
}
}
En el ejemplo anterior podemos ver como usarlo en un MatDialog.
Carteles
Message service
Este servicio de mensajes es el que centraliza todos los llamados a los carteles de alerta, dialogo, etc. Gracias a este servicio podemos mantener un unico estilo de carteles, alertas y formas para todas las aplicaciónes, pre construidos.
Ejemplo:
messageServiceTest(hero: Hero) {
this.messageSerivce.showError(['ERROR ::: Nombre: '+ hero.name, 'ERROR ::: Id: '+hero.id]).subscribe(() => {
this.messageSerivce.showConfirmDialog('Heroe: ' + hero.name +' <p>Id: ' + hero.id +'</p>', 'Dialogo INFO!! Detalle del Heroe').subscribe(() => {
this.messageSerivce.showAlertConfirmDialog('Heroe: ' + hero.name +' <p>Id: ' + hero.id +'</p>', 'Alerta!! Detalle del Heroe').subscribe(() => {
this.messageSerivce.showInfo('Info:: Heroe: ' + hero.name );
});
});
});
}
Indicadores de carga de información
Load service
Este servicio expone distintos metodos a los cuales se deben subscribir para poder reconocer cuando se esta ejecutando un request cualquiera y cuando termina. Esta pensado para que se indique al usuario de alguna manera que su pedido esta siendo procesado y que se esta esperando una respuesta del servicio web.
| Metodo | Parametros | Retorno | Observaciones | | :------ | :--------- | :------ | :------------ |
Pignus
Para poder conectarse con pignus necesitamos crear un componente y tenerlo vinculado a la ruta "/login". De esta manera se realiza con Angular Router:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [ {
path: 'login',
component: LoginTestComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
El componente LoginTestComponent debe ser un componente especial que se crea de una manera especifica, extendiendo de la clase PignusLogin, proveida desde la libreria. Donde la clase LoginTestComponent quedaria de la siguiente manera:
import { Component} from '@angular/core';
import { ActivatedRoute, Router, NavigationExtras } from '@angular/router';
import { LoadService, PignusLogin, AuthPignusService } from '@jussanjuan/angular-pjsj';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-login-test',
templateUrl: './login-test.component.html',
styleUrls: ['./login-test.component.sass']
})
export class LoginTestComponent extends PignusLogin {
constructor( authService: AuthTestService,
private router: Router,
activatedRoute: ActivatedRoute,
private loadService: LoadService) {
super(authService, activatedRoute);
}
/**
* Este metodo se invoca cuando detecta un acceso no permitido a la plataforma.
* Desde este espacio se debe siempre redirigir a la ruta de login de pignus, pero tambien se pueden ejecutar
* validaciones de datos, generar carteles, etc.
* Pero sin redirigir a Pignus no se va a poder utilizar las acciones que sean privadas en un servidor
*
*/
public redirectNotAuth() {
console.log(LoginTestComponent.toString() + ": No autenticado")
// urlLogin es la url de login de Pignus
window.location.href = environment.urlLogin;
}
/**
* Este metodo se invoca cuando se autentica correctamente con pignus.
* Desde este espacio se pueden ejecutar diferentes tipos de redirecciones, validaciones y demás.
*
*/
public successAuth() {
console.log(LoginTestComponent.toString() + ": Autenticado")
// Cuando se autentique correctamente, es necesario redirigirlo a la ruta que intentaba acceder. O en esta caso siempre al home
this.router.navigate(['/home']);
}
}
Como podemos ver en el ejemplo tenemos que implementar dos métodos abstractos, donde cada uno tiene su acción especifica a ejecutar y que se ejecutan en lugares especificos.
Además tenemos que tener en cuenta que existe una buena practica y es la de agregar como propiedad de configuración del entorno la ruta de redirección a Pignus. Ya que para QA es una ruta, para desarrollo es otra y para producción otra.
Para lo cual necesitamos ingresar una entrada en el archivo de src/app/enviroments/enviroment.ts para desarrollo/QA y en src/app/enviroments/enviroment-prod.ts para producción.
Un ejemplo de la configuración de la ruta de redirección es la siguiente:
export const environment = {
production: false,
urlLogin : 'http://${direccion-servidor-pignus}/api/?publickey=${public-key-sistema-en-cuestion}'
};
ADVERTENCIA!! NO se debe implementar la interfaz OnInit en el componente de Login ya que esta implementada en la clase abstracta y es usado el Metodo NgOnInit() para ejecutar lógica especifica de autenticación.
Especificacion de la clase PignusLogin | Método | Abstracto | Observaciones | | :------ | :-------- | :------------ | | NgOnInit | No | Metodo sobre escrito desde la interzaf OnInit y es el encargado de iniciar la ejecución del método initalEvaluate | | initalEvaluate | No | Método que ejecuta la lógica necesaria para saber si el usuario esta logueado o no. En caso de tener authcode y no JWT, invoca al metodo exchange del AuthPignusService. Si no tiene authcode, invoca el método abstracto redirectNotAuth, en caso de reconocer que esta todo bien invoca al método abstracto successAuth | | redirectNotAuth | Si | Este metodo se invoca cuando detecta un acceso no permitido a la plataforma. Desde este espacio se debe siempre redirigir a la ruta de login de pignus, pero tambien se pueden ejecutar validaciones de datos, generar carteles, etc. Pero sin redirigir a Pignus no se va a poder utilizar las acciones que sean privadas en un servidor. | | successAuth | Si | Este metodo se invoca cuando se autentica correctamente con pignus. Desde este espacio se pueden ejecutar diferentes tipos de redirecciones, validaciones y demás. |
Interceptor
Por último agregar interceptor, ya que sin agregarlo lo anterior no va a funcionar correctamente. Este es el que llama la ruta /login cuando encuentra un problema de permisos.
@NgModule({
...
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: PignusHttpInterceptor,
multi: true
}
],
....
})
export class AppModule { }
Code scaffolding
Run ng generate component component-name --project angular-pjsj
to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module --project angular-pjsj
.
Note: Don't forget to add
--project angular-pjsj
or else it will be added to the default project in yourangular.json
file.
Build
Run ng build angular-pjsj
to build the project. The build artifacts will be stored in the dist/
directory.
Publishing
After building your library with ng build angular-pjsj
, go to the dist folder cd dist/angular-pjsj
and run npm publish
.
Running unit tests
Run ng test angular-pjsj
to execute the unit tests via Karma.
Further help
To get more help on the Angular CLI use ng help
or go check out the Angular CLI README.