libs/suspense/types/types.ts
Clase abstracta que define la estructura de un componente de tipo Suspenseable. Considera de manera muy inocente, todos los posibles casos de uso, y funciones posiblemente requeridas. Es abstracta de modo de a lo menos obligar la implmentación de las interfaces OnInit y OnDestroy.
Propiedades |
Métodos |
|
broadcastError |
broadcastError()
|
Definido en libs/suspense/types/types.ts:171
|
Devuelve :
void
|
broadcastLoad |
broadcastLoad()
|
Definido en libs/suspense/types/types.ts:159
|
Devuelve :
void
|
eventHandler | ||||||
eventHandler(eventName: string)
|
||||||
Definido en libs/suspense/types/types.ts:150
|
||||||
Parámetros :
Devuelve :
void
|
init |
init()
|
Definido en libs/suspense/types/types.ts:137
|
Devuelve :
void
|
Abstract ngOnDestroy |
ngOnDestroy()
|
Definido en libs/suspense/types/types.ts:125
|
Devuelve :
void
|
Abstract ngOnInit |
ngOnInit()
|
Definido en libs/suspense/types/types.ts:124
|
Devuelve :
void
|
setup |
setup()
|
Definido en libs/suspense/types/types.ts:141
|
Devuelve :
ObservableInput<any>
|
renderComponenteReady |
renderComponenteReady()
|
Heredado desde
SuspenseableRenderer
|
Definido en
SuspenseableRenderer:110
|
En caso que la operación sea desde dentro de un browser, se cambia el valor de
Devuelve :
void
|
Opcional eventName |
Tipo : string
|
Definido en libs/suspense/types/types.ts:135
|
Nombre del evento a capturar, en caso de operar en modo "reactivo" |
eventService |
Tipo : EventService
|
Valor por defecto : inject(EventService)
|
Definido en libs/suspense/types/types.ts:130
|
Servicio de eventos, requerido para el modo de operación "reactivo" |
defaultDisplay |
Tipo : string
|
Valor por defecto : 'inherit'
|
Heredado desde
SuspenseableRenderer
|
Definido en
SuspenseableRenderer:92
|
elementRef |
Tipo : ElementRef
|
Valor por defecto : inject(ElementRef)
|
Heredado desde
SuspenseableRenderer
|
Definido en
SuspenseableRenderer:89
|
platformId |
Tipo : Object
|
Valor por defecto : inject(PLATFORM_ID)
|
Heredado desde
SuspenseableRenderer
|
Definido en
SuspenseableRenderer:90
|
renderer |
Tipo : Renderer2
|
Valor por defecto : inject(Renderer2)
|
Heredado desde
SuspenseableRenderer
|
Definido en
SuspenseableRenderer:88
|
import { isPlatformBrowser } from '@angular/common';
import { ElementRef, inject, Injectable, InjectionToken, OnDestroy, OnInit, PLATFORM_ID, Renderer2, Type } from '@angular/core';
import { ObservableInput } from 'rxjs';
import { EventService, SuspenseCacheService } from '@queplan/qp-suspense/services';
import { SuspenseableBroadcaster } from './suspenseable-broadcaster';
import { SuspenseableClassic } from './suspenseable-classic';
import { SuspenseableEventDriven } from './suspenseable-event-driven';
export const SUSPENSE = new InjectionToken('SUSPENSE');
/**
* Define la estructura esperada para un componente al que se pueda aplicar la
* lógica tipo Suspense (al estilo React, o Vue)
* @see https://netbasal.com/adding-suspense-to-angular-%EF%B8%8F-%EF%B8%8F-1f34fd603584
*/
export interface ISuspenseable {
/**
* Funcion de configuración o preparación de un componente de tipo Suspenseable
* Debe responder al momento en que el componente esté efectivamente en condiciones de
* ser desplegado.
*/
setup(): ObservableInput<any>;
/**
* Cabe la posibilidad que el componente esté en un estado "listo para poder desplegarse"
* una vez que se hayan ejecutado ciertos procesos, que serán capturados a través de los eventos:
* - <eventName>:load una vez que el componente este listo
* - <eventName>:error en caso que deba informarse un error
* Este tipo de operación podríamos interpretarlo como reactivo, ya que son eventos que
* disparan ciertas acciones.
* La implementación recomendada para esta función es:
* ```
* eventHandler(eventName: string): void {
* this.eventName = eventName;
* }
* ```
* Si la variable eventName del componente Suspenseable no tiene valor definido, la carga del
* componente buscará realizarse luego de llamar la función `setup()`
* @param eventName Nombre que servirá para identificar el evento a capturar
*/
eventHandler(eventName: string): void;
/**
* En caso de estar habilitado el modo "reactivo" (habiendo definido el nombre del evento a capturar
* en la variable eventName del componente de tipo Suspenseable), la función informará el estado de carga
* como listo.
*/
broadcastLoad(): void;
/**
* En caso de estar habilitado el modo "reactivo" (habiendo definido el nombre del evento a capturar
* en la variable eventName del componente de tipo Suspenseable), la función informará un estado de error
* en la carga del componente.
*/
broadcastError(): void;
/**
* Funcion encargada de la inicialización del componente.
* Pensada para absorber las operaciones que realiza ngOnInit().
* Su implementación es opcional.
*/
init(): void;
}
/**
* Define la estructura que debe implementar un módulo que declare un componente Suspenseable no standalone
*/
export interface SuspenseableModule {
/**
* Debe retornar el componente Suspenseable que está declarado en el módulo
*/
getComponent(): Type<ISuspenseable>;
}
/**
* Un componente de tipo Suspenseable, dependiendo de como sea inyectado en la vista, pudiera ser desplegado
* antes que se encuentre en un estado listo para ser desplegado. Dado que eso es un error, se define una
* clase que se encargará de que el componente no sea desplegado hasta que se encuentre en un estado listo.
* Al momento de cargar el componente, y de iniciar el proceso del constructor, y flujos del ciclo de vida de
* Angular (ngOnInit, ngAfterViewInit, etc), el componente ya se encuentra cargado en memoria, sin embargo se
* desconoce su estado. Estando en memoria, para eviar que se despliegue, se oculta el componente vía CSS usando
* `display: none`, y se desplegará, cambiando a `display: inherit` ó su valor inicial, una vez que se encuentre
* en un estado listo.
*
* Todos los subtipos de Suspenseable extienden de esta clase.
*/
export class SuspenseableRenderer {
renderer: Renderer2 = inject(Renderer2);
elementRef: ElementRef = inject(ElementRef);
platformId: Object = inject(PLATFORM_ID);
defaultDisplay: string = 'inherit';
/**
* En caso que la operación sea desde dentro de un browser, se rescatará el valor CSS inicial/original de `display`
* del componente, y se cambiará a none, de esa manera se oculta el componente (asumiendo un estado *no listo*).
*/
constructor() {
if(isPlatformBrowser(this.platformId)) {
this.defaultDisplay = window.getComputedStyle(this.elementRef.nativeElement).getPropertyValue('display');
this.renderer.setStyle(this.elementRef.nativeElement, 'display', 'none');
}
}
/**
* En caso que la operación sea desde dentro de un browser, se cambia el valor de `display` a su valor inicial; de
* esa manera se despliega el componente (asumiendo un estado *listo para ser desplegado*).
*/
renderComponenteReady() {
if(isPlatformBrowser(this.platformId)) {
this.renderer.setStyle(this.elementRef.nativeElement, 'display', this.defaultDisplay);
}
}
}
/**
* Clase abstracta que define la estructura de un componente de tipo Suspenseable.
* Considera de manera muy inocente, todos los posibles casos de uso, y funciones posiblemente requeridas.
* Es abstracta de modo de a lo menos obligar la implmentación de las interfaces OnInit y OnDestroy.
*/
@Injectable()
export abstract class Suspenseable extends SuspenseableRenderer implements ISuspenseable, OnInit, OnDestroy {
abstract ngOnInit(): void;
abstract ngOnDestroy(): void;
/**
* Servicio de eventos, requerido para el modo de operación "reactivo"
*/
eventService : EventService = inject(EventService);
/**
* Nombre del evento a capturar, en caso de operar en modo "reactivo"
*/
eventName ?: string;
init() {
throw new Error('init() no implementado');
}
setup(): ObservableInput<any> {
/**
* Permite que en un contexto de desarrollo sepamos que este método no
* está implementado => despliegue de estado de error para carga del componente
* Aplica solamente para el modo de operación "normal" (usando función `setup()` )
*/
throw new Error('setup() no implementado');
}
eventHandler(eventName: string): void {
/**
* Permite que en un contexto de desarrollo sepamos que este método no
* está implementado => despliegue de estado de error para carga del componente
* Aplica solamente para el modo de operación "reactivo" (variable `eventName` con valor definido )
*/
throw new Error('eventHandler() no implementado');
}
broadcastLoad(): void {
/**
* Sólo notifica el estado en caso de estar operando en modo "reactivo"
*/
if (this.eventName) {
this.renderComponenteReady();
this.eventService.broadcast(`${this.eventName}:load`, true);
} else {
throw new Error('Usando modo reactivo sin haber defido el nombre del evento. Verifique implementación de eventHandler()');
}
}
broadcastError(): void {
/**
* Sólo notifica el estado en caso de estar operando en modo "reactivo"
*/
if (this.eventName) {
this.renderComponenteReady();
this.eventService.broadcast(`${this.eventName}:error`, true);
} else {
throw new Error('Usando modo reactivo sin haber defido el nombre del evento. Verifique implementación de eventHandler()');
}
}
}
/**
* Clase abstracta que define la estructura de un componente de tipo Suspenseable aplicable cuando el componente se encuentra
* definido dentro de un módulo. A priori se desconoce que subtipo de componente Suspenseable será, por lo que lo menos malo es
* usar la clase abstracta Suspenseable, y definir explícitamente todos los métodos que se requieran.
*/
export abstract class SuspenseableInModule extends Suspenseable {}
/**
* Tipo especial para la respuesta del import de un componente definido como Suspenseable.
* Usando el atributo default se evita requerir expresamente el nombre del componente.
*/
export type TDefaultSuspenseable = { default: Type<Suspenseable> };
/**
* Tipo de la promesa utilizada en la directiva defaultView para la carga de un componente Suspenseable.
* Componentes Suspenseable "normales" son de tipo Promise<TDefaultSuspenseable>
* Componentes Suspenseable "standalone" o aquellos que cuyo factory sea el módulo donde están definidos son de tipo Promise<Type<unknown>>
*
*/
export type SuspenseFactoryPromise = () => Promise<TDefaultSuspenseable> | Promise<Type<unknown>>;
/**
* Tipo general para los componentes de tipo Suspenseable.
* Sabemos que es Suspenseable, pero no sabemos que tipo de Suspenseable es.
*/
export type TSuspenseable = Type<Suspenseable | SuspenseableClassic | SuspenseableEventDriven | SuspenseableBroadcaster | SuspenseableInModule>;
/**
* Se utiliza como proveedor de un componente de tipo Suspenseable que es cargado de modo "no lazy" (es decir sin un import explícito).
* En este caso, lo más habitual es que los componentes esten definidos dentro de un módulo.
* @param comp {TSuspenseable} Componente de tipo Suspenseable
* @returns
*/
export const useSuspense = (comp: TSuspenseable) => ({
provide: SUSPENSE,
useExisting: comp,
});
/**
* InjectionToken para servicio de eventos.
* Se define de esta manera para asegurarse que sea efectivamente un Singleton, y que sea posible reutilizar
* el objeto.
*/
export const EVENT_SERVICE = new InjectionToken<EventService>('EventService', {
providedIn: 'root',
factory: () => {
// console.log('EVENT_SERVICE', new Date());
return inject(EventService);
}
});
/**
* InjectionToken para servicio de caché para clases de componentes importados dinamicamente.
* Se define de esta manera para asegurarse que sea efectivamente un Singleton, y que sea posible reutilizar
* el objeto.
*/
export const SUSPENSE_CACHE = new InjectionToken<SuspenseCacheService<any>>('SuspenseCache', {
providedIn: 'root',
factory: () => {
// console.log('SUSPENSE_CACHE', new Date());
return inject(SuspenseCacheService<any>);
}
});
/**
* Función deno-operación que permite deshabilitar la consola del navegador.
*/
const _noOp = () => {};
/**
* Variable de respaldo de la consola del navegador en si estado original.
*/
const originalConsole = console;
/**
* InjectionToken para habilitar o deshabilitar la consola del navegador.
* Si está en true se habilita, si está en false se deshabilita. Se utiliza como `provider` en el
* `app.module` de la aplicación.
* ```
* providers: [
* {
* provide: DEBUG_SUSPENSE,
* useValue: false // true para habilitar la consola del navegador
* },
* ],
* ```
*/
export let DEBUG_SUSPENSE = new InjectionToken<boolean>('DEBUG_SUSPENSE',{
providedIn: 'root',
factory: () => false
});
/**
* Habilita o deshabilita la consola del navegador en función del parámetro que reciba.
* @param debug {boolean} Indica si se debe habilitar o deshabilitar la consola del navegador.
* @returns Función de console a utilizar.
*/
export const toggleConsole = (debug: boolean = false): Console => {
let newConsole: Console = originalConsole;
if (!debug) {
(newConsole as any) = {
log : _noOp,
warn : _noOp,
debug: _noOp,
info : _noOp,
};
}
return newConsole;
};
/**
* InjectionToken para exponer la consola del navegador.
* La consola que será disponibilizada será bajo el contexto de la líbrería, de modo de no afectar el comportamiento
* de las demás aplicaciones.
*/
export const SUSPENSE_LOG = new InjectionToken<Console>('SUSPENSE_LOG',
{
providedIn: 'root',
factory : () => toggleConsole(inject(DEBUG_SUSPENSE))
});