¿Conocías la utilidad "isPlatformBrowser"?
Dentro del mundo del desarrollo conocemos una serie de buenas prácticas que intentamos tener en cuenta para que la evolución del proyecto y su escalabilidad se conviertan en algo sencillo. Mejoramos la legibilidad, la reutilización, la disminución de errores. En definitiva si simplificamos la vida en nuestro trabajo con foco en la calidad, los problemas y piedras en el camino tienden a ser menos.
Uno de los principios más básicos y que mejor funcionan es el “principio de responsabilidad única”. Nos dice que una clase debe tener una responsabilidad determinada, debe tener un objetivo o función particulares y no varios, debe ser una lógica autocontenida para la consecución de un fin predeterminado. Y cuando nos centramos en el framework de desarrollo Angular basado en Server Side Rendering (SSR), nos topamos con el principio de responsabilidad ¿doble?
Voy a explicarme. Al trabajar con desarrollos de frontales basados en SPAs pero que reciben una primera versión renderizada en el navegador proveniente del servidor tenemos un nuevo paradigma dentro del desarrollo de software. Ahora nuestro código no solo se ejecuta en el navegador. Hay partes susceptibles de ser ejecutadas (y por tanto renderizadas) desde el propio servidor, por ejemplo, mediante el uso de Angular Universal + NODE.
Para ayudarnos en la tarea, el equipo de Angular ha puesto a nuestra disposición un dardo envenenado, nos referimos a “isPlatformBrowser”. Esta utilidad nos permite controlar en un trozo de código qué parte del mismo se ejecutará en la versión del servidor y qué parte se ejecutará en la versión del navegador. Es de agradecer para que tengamos un control total sobre la ejecución de nuestro software.
if (isPlatformBrowser(this.platformId)) {
//Código de ejecución en navegador
} else {
//Código de ejecución en servidor
}
Supongamos que tenemos una parte de nuestro software que necesita acceder al localStorage. Debemos asegurar que solo se ejecute en la versión del navegador. De hacerlo en la del servidor tendremos un “castañazo” épico al intentar acceder desde servidor a algo inexistente, a un API propio del browser. Aquí justo es donde saboreamos el veneno de isPlatformBrowser.
Nos pone en el camino de bifurcar el código, de tener clases con el principio de responsabilidad doble:
¿Y cómo podemos resolver este dilema? Fácil, la respuesta siempre estuvo ahí, se llama inyección de dependencias (DI). El framework de Angular está enteramente basado en este paradigma, lo usamos continuamente añadiendo servicios en los constructores de nuestros componentes y el framework se encarga de ubicar el servicio que necesitamos, así como de aprovisionarlo devolviendo una instancia del servicio «automágicamente». Tenemos así los servicios usables, inyectados en nuestros componentes con una simple declaración de uso.
@Component({ ... })
class HeroListComponent {
constructor(private service: HeroService) {}
}
En el ejemplo, el servicio HeroService queda aprovisionado cuando se instancie el componente HeroListComponent, y todo gracias a la inyección de dependencias.
Pero no hemos de olvidarnos que este paradigma va más allá, y de la misma forma que el framework nos provisiona de todo lo que necesitamos, nosotros podemos utilizarlo para provisionar clases de forma más controlada. Un ejemplo clásico es el de los servicios, que en ocasiones no son singleton (una única instancia para todo el proyecto) y los provisionamos en el apartado “providers”.
@NgModule({
providers: [UserService],
})
export class UserModule {
}
Rizando el rizo, vamos a ver un ejemplo que hemos puesto en práctica para el proyecto que sirve esta misma web que estás leyendo. Tenemos la necesidad de realizar una operativa al arrancar la aplicación Angular, que es traernos un sitemap para establecer de forma dinámica el routing de la aplicación. Esto es debido a que usamos Wordpress + Angular y por tanto tenemos un CMS Headless.
En nuestro proyecto necesitamos una versión de configuración que se ejecute en el servidor:
En la versión ejecutada en el navegador necesitaremos:
Como veis, aunar esta funcionalidad en una misma clase requeriría un código extenso, una mezcla de funcionalidades, y el principio de responsabilidad única quedaría diluido con el uso de isPlatformBrowser; cierto es que la clase tendría el objetivo de la “carga inicial”, pero no menos cierto es que esa carga no es la misma en cada plataforma de ejecución. Y aquí es cuando conviene huir de nuestro amigo “isPlatformBrowser” mediante inyección de dependencias de una forma limpia y sencilla:
Tenemos nuestro módulo app.module.ts que es la definición de aplicación Angular para el navegador. Uno de los servicios que usamos para el proceso inicial de carga de configuraciones es el SettingsBrowserService.
En un modelo SSR, también tenemos el fichero app.server.module.ts que es el módulo principal de la aplicación Angular en su versión del servidor. Y aquí justo es donde podemos volver al paradigma del principio de responsabilidad única. Mediante el uso de provide y useClass, estamos dando el cambiazo e indicando al framework que cuando arranque la aplicación desde el servidor, se sustituya la clase SettingsBrowserService por la clase SettingsServerService.
Ahora tenemos dos clases para la configuración inicial o de carga; una solo para la versión del navegador y una solo para la versión del servidor. Cada clase tiene una responsabilidad específica y única, realizar la carga y configuración en su plataforma de ejecución, y nada más.
Esperamos que te haya resultado interesante este post y ya sepas de la utilidad isPlatformBrowser.