El Angular Compatibility Compiler (ngcc) es una herramienta esencial en la migración de aplicaciones Angular al nuevo motor de compilación Ivy. Su objetivo es proporcionar una transición suave desde View Engine, el compilador anterior de Angular, hacia Ivy, asegurando la compatibilidad con bibliotecas y aplicaciones que no han sido actualizadas a las nuevas versiones de Angular. Esta herramienta es particularmente importante en proyectos grandes y complejos donde se mantienen dependencias externas que aún no han sido adaptadas a Ivy.

En este contexto, el ngcc permite que las bibliotecas de terceros que aún no han sido compiladas con Ivy se puedan usar sin problemas, evitando errores y asegurando que los componentes, directivas y servicios funcionen correctamente en el nuevo motor. Esto permite a los desarrolladores aprovechar las ventajas de Ivy, como la mejora en la optimización del árbol de dependencias, la reducción del tamaño del bundle, y un rendimiento significativamente mejorado sin perder compatibilidad con bibliotecas más antiguas.

El uso del Angular Compatibility Compiler no se limita a la simple ejecución de un comando. En entornos de integración continua y despliegue continuo (CI/CD), su integración es fundamental para automatizar el proceso de actualización y garantizar que la compatibilidad con Ivy se mantenga en todas las fases del desarrollo. Además, el uso del ngcc en un flujo de trabajo CI/CD permite asegurar que las nuevas dependencias o actualizaciones en bibliotecas no rompan la compatibilidad con el sistema base del proyecto, lo que ahorra tiempo y recursos al evitar errores en producción.

Un aspecto clave que se debe considerar al trabajar con ngcc es que su función no se limita a compilar las bibliotecas de terceros. También tiene la capacidad de optimizar la carga y el rendimiento de las aplicaciones Angular. Por ejemplo, en proyectos de gran escala o monorepositorios donde se manejan múltiples aplicaciones, el ngcc juega un papel crucial para garantizar que solo se carguen las partes necesarias de la aplicación, lo cual mejora la eficiencia general. La configuración del ngcc también permite personalizar cómo se manejan las bibliotecas de terceros, lo cual es útil en escenarios complejos como monorepositorios, donde se pueden tener aplicaciones compartidas que requieren un control más granular sobre qué partes de la biblioteca se deben incluir.

La migración de View Engine a Ivy no es simplemente una cuestión de cambio de compilador. El Angular Compatibility Compiler es también una herramienta para comprender las optimizaciones que ofrece Ivy. Por ejemplo, el sistema de detección de cambios ha sido significativamente mejorado con Ivy, lo que reduce la carga en la aplicación y mejora la respuesta del sistema. Además, las nuevas capacidades de Ivy permiten optimizar el rendimiento en dispositivos móviles, lo cual es crucial para muchas aplicaciones modernas que buscan mantener una experiencia de usuario fluida, incluso en dispositivos de bajo rendimiento.

Otro punto relevante es cómo el ngcc interactúa con las opciones de configuración del compilador en Angular. El uso de diferentes configuraciones como "strict mode" y las mejoras en la detección de errores de compilación también influyen en la eficacia del ngcc. Las opciones de configuración ayudan a los desarrolladores a controlar el comportamiento de la compilación, lo que facilita la gestión de proyectos más grandes o con requisitos específicos.

El avance hacia el uso exclusivo de Ivy también incluye la mejora en las pruebas unitarias y la integración con herramientas como TestBed. La compatibilidad con Ivy permite mejorar la seguridad de tipo en las pruebas, lo cual facilita la identificación de errores en etapas tempranas del desarrollo. El uso del ngcc en este contexto asegura que las bibliotecas de prueba sean completamente compatibles con las nuevas funcionalidades de Ivy, lo que simplifica el proceso de validación de componentes y servicios en Angular.

Además de los aspectos técnicos, es importante que los desarrolladores comprendan la magnitud de los cambios que Ivy trae consigo. Aunque muchas de las mejoras son invisibles para el usuario final, los beneficios a nivel de rendimiento, tamaño de archivo y velocidad de desarrollo son notables. La adopción de Ivy implica que las aplicaciones no solo serán más rápidas, sino también más fáciles de mantener y escalar. A medida que se vayan incorporando nuevas características y herramientas en Angular, Ivy seguirá siendo el motor central sobre el cual se construirán estas innovaciones, por lo que entender cómo funciona y cómo integrarlo correctamente es esencial para maximizar sus beneficios.

¿Cómo crear una aplicación interactiva con temas dinámicos usando propiedades personalizadas de CSS en Angular?

En el desarrollo de aplicaciones modernas, uno de los aspectos más importantes es proporcionar una experiencia de usuario dinámica y personalizable. Un caso común en aplicaciones web es la implementación de temas interactivos, donde los usuarios pueden modificar la apariencia de la interfaz según sus preferencias. Esta capacidad no solo mejora la accesibilidad, sino que también permite que la aplicación se ajuste a diferentes contextos de uso, como la preferencia por modos de color oscuro o claro.

En la aplicación Angular Academy, se implementa un sistema de cambio de tema utilizando propiedades personalizadas de CSS, lo que permite una experiencia interactiva sin la necesidad de recurrir a un preprocesador CSS como SCSS. Este enfoque proporciona una mayor flexibilidad y facilidad para aplicar cambios de estilo en tiempo real, lo que resulta ideal para entornos donde la personalización es clave.

Uso de propiedades personalizadas de CSS para temas dinámicos

En primer lugar, las propiedades personalizadas de CSS, también conocidas como CSS Custom Properties o variables CSS, permiten definir valores reutilizables dentro de las hojas de estilo de la aplicación. En el contexto de Angular, esta capacidad se extiende mediante la vinculación dinámica de propiedades CSS a variables definidas en el componente.

Por ejemplo, si un usuario desea cambiar el color del fondo del encabezado, el valor de esta propiedad puede ser ajustado dinámicamente usando un componente llamado "theme picker". Este componente proporciona una interfaz de usuario para seleccionar el color deseado, y el valor seleccionado se guarda en el almacenamiento local del navegador para que se mantenga entre sesiones.

El código para vincular las propiedades personalizadas en Angular es sencillo. Se utiliza el decorador @HostBinding para aplicar las propiedades CSS a nivel de componente. Esto permite modificar, por ejemplo, el color de fondo del encabezado de manera reactiva en función de las elecciones del usuario:

typescript
export class AppComponent {
@HostBinding('style.--background') background: string;
@HostBinding('style.--headerbackground') headerBackground: string;
@HostBinding('style.--tilebackground') tileBackground: string; constructor(themeService: ThemeService) {
this.background = themeService.getSetting('background');
this.headerBackground = themeService.getSetting('headerBackground');
this.tileBackground = themeService.getSetting('tileBackground');
} }

Implementación del servicio de temas

Para que la experiencia de cambio de tema sea persistente, es necesario crear un ThemeService que maneje la recuperación y almacenamiento de las configuraciones de tema seleccionadas por el usuario. Este servicio se encarga de gestionar los valores de las propiedades CSS personalizadas utilizando localStorage. De esta forma, los cambios no solo se aplican durante la sesión actual, sino que se mantienen incluso después de cerrar el navegador.

El servicio podría verse de la siguiente manera:

typescript
@Injectable({ providedIn: 'root', }) export class ThemeService { constructor() {} public setSetting(name: string, value: string): void { localStorage.setItem(name, value); }
public getSetting(name: string): string {
switch (name) { case 'background': return localStorage.getItem(name) || 'yellow'; case 'tileBackground':
return localStorage.getItem(name) || '#ffcce9';
case 'headerBackground': return localStorage.getItem(name) || '#00aa00'; } return 'white'; } }

Este servicio asegura que los valores predeterminados sean asignados en caso de que el usuario no haya configurado aún un tema personalizado. Además, ofrece la posibilidad de expandir la implementación para integrar otras fuentes de configuración, como un sistema de tokens de diseño corporativo o una base de datos remota.

Controlando la disposición de los elementos con CSS Grid

El uso de CSS Grid también juega un papel importante en la creación de interfaces personalizables. Con el avance de las herramientas de diseño web, se puede definir una disposición flexible y dinámica de los elementos de la página, lo que permite optimizar el uso del espacio según las necesidades del usuario. Al igual que con los colores, las dimensiones de los elementos pueden ser ajustadas en tiempo real utilizando propiedades CSS personalizadas.

Supongamos que en una aplicación de visualización de videos, se desea cambiar el tamaño del área de texto y reducir el espacio asignado al video. Esta operación se puede realizar de manera sencilla utilizando lógica en TypeScript que calcule las proporciones dinámicamente en función de las preferencias del usuario, todo mientras se ajustan las propiedades CSS correspondientes:

typescript
@HostBinding('style.--text-size') textSize: string;
@HostBinding('style.--video-size') videoSize: string;

De este modo, es posible cambiar el tamaño de los elementos y la distribución de la interfaz sin tener que modificar manualmente las hojas de estilo, brindando una experiencia más flexible y adaptativa.

Además de estas consideraciones técnicas, es crucial que el lector comprenda la importancia de separar la lógica de gestión del estado de la interfaz. Utilizar un servicio para gestionar los temas permite abstraer las complejidades del manejo de configuraciones, dejando a los componentes centrarse únicamente en la representación visual y en la interacción del usuario. Esto también facilita el mantenimiento y la escalabilidad de la aplicación a medida que se agregan más configuraciones o temas adicionales.

¿Cómo estructurar una aplicación Angular utilizando componentes, servicios y módulos?

El desarrollo de aplicaciones en Angular se basa en una estructura modular que permite separar de manera eficiente las distintas responsabilidades de la aplicación. Este enfoque no solo facilita la reutilización del código, sino que también mejora la organización y escalabilidad del proyecto. En el caso de una aplicación como Angular Academy, es crucial dividir la funcionalidad en componentes específicos, servicios y módulos para mantener el código limpio y modular. Este proceso involucra varios pasos, que incluyen la creación de modelos de datos, la división en componentes, la gestión de rutas y la configuración de los servicios para la recuperación de datos.

Uno de los pasos más importantes al desarrollar una aplicación Angular es establecer un modelo de datos claro antes de comenzar a utilizar los componentes. Los modelos proporcionan una estructura coherente para los datos que se utilizarán a lo largo de la aplicación, y en este caso, los modelos principales son: Escuela, Curso y Video. La idea es separar la recuperación y almacenamiento de los datos de su uso en los diferentes componentes de la aplicación.

Para describir estos modelos, utilizamos interfaces de TypeScript. A continuación se presentan ejemplos de cómo se definen los modelos:

typescript
export interface ICourse { id: string; title: string; description?: string; videos: IVideo[]; } export interface IVideo { externalId: string; // ID de YouTube title: string; date: Date; author?: string; description?: string; } export interface ISchool { id: string; name: string; longitude: number; latitude: number; courses: ICourse[]; }

Estos modelos permiten que la aplicación gestione la información de las escuelas, los cursos y los videos de manera estructurada. Por ejemplo, cada Curso tiene un título, una descripción opcional y una lista de videos, mientras que cada Video contiene datos como su identificador en YouTube, el título, la fecha de carga y un autor. Cada Escuela tiene un nombre y coordenadas geográficas, además de una lista de cursos ofrecidos.

A continuación, la aplicación se divide en tres módulos principales que se muestran por separado en la interfaz de usuario: Curso, Tema y Escuelas. Cada módulo se encarga de una parte específica de la funcionalidad de la aplicación.

El componente Curso utilizará una cuadrícula de diseño para mostrar los videos, y el componente Tema permitirá a los usuarios ajustar la apariencia de la interfaz utilizando propiedades CSS personalizadas. El componente Escuelas permitirá al usuario encontrar su escuela mediante un mapa interactivo (utilizando Angular Google Maps) y seleccionar un curso de esa escuela.

Es esencial que cada uno de estos componentes esté vinculado con sus respectivos módulos en el archivo de rutas app.module.ts:

typescript
const routes: Routes = [
{ path: '', redirectTo: 'course/1', pathMatch: 'full' },
{ path: 'course/:id', component: CourseComponent },
{
path: 'schools', component: SchoolsComponent },
{ path: 'theme', component: ThemeComponent },
];

Aquí se define una ruta predeterminada que muestra un curso con el ID 1, lo que facilita la navegación sin necesidad de una página de inicio de sesión. Esto se puede ampliar en versiones posteriores, incorporando un sistema de autenticación.

Una vez que la aplicación se ha dividido en componentes y se han especificado las rutas, el siguiente paso es configurar los módulos y sus dependencias. Cada módulo de la aplicación incluirá solo las dependencias que necesita, lo que ayuda a mantener la estructura del proyecto limpia y fácil de entender. Por ejemplo, el módulo Curso importará el módulo de Video, mientras que el módulo Escuelas utilizará el módulo de Google Maps.

La recuperación de datos se gestionará a través de servicios específicos que interactúan con el backend. Los servicios se encargarán de obtener los datos de manera asíncrona. En este caso, el CourseService recuperará un único curso, mientras que el SchoolsService manejará la obtención de varios cursos ofrecidos por diferentes escuelas.

typescript
@Injectable({ providedIn: 'root' })
export class CourseService { constructor() {} getCourse(courseId: string): Observable<ICourse> { return of(mockCourse); // Devuelve datos simulados } }

Los servicios permiten que los componentes de Angular accedan de manera sencilla a los datos sin tener que preocuparse por la implementación interna de la recuperación de estos.

Por último, una vez que se ha definido la estructura de los módulos, rutas y servicios, es necesario configurar la navegación entre los componentes. Esto se logra utilizando el componente Navigation, que proporciona enlaces de navegación entre las secciones de la aplicación.

Es importante entender que este enfoque modular no solo facilita el desarrollo y mantenimiento de la aplicación, sino que también permite escalarla fácilmente en el futuro. A medida que la aplicación crece, puedes agregar nuevos componentes, servicios y módulos sin que se vea comprometida la estructura general del proyecto.

Además de la arquitectura de la aplicación, es fundamental tener en cuenta la importancia de los componentes reutilizables y cómo estos pueden interactuar entre sí. Los componentes deben diseñarse de manera que sean lo más independientes posible, lo que significa que deberían tener sus propios modelos de datos y lógica de negocio, sin depender demasiado de otros componentes. Esto facilita la prueba y la reutilización del código a lo largo del proyecto.

¿Cómo usar el Angular Compatibility Compiler y el Angular Linker en tu flujo de trabajo de desarrollo?

El Angular Linker es una herramienta que reemplaza al Angular Compatibility Compiler (ngcc), y su propósito es convertir una biblioteca Angular que ha sido compilada parcialmente con Ivy en una biblioteca completamente compatible con Ivy, antes de que sea incluida en la compilación de nuestra aplicación. Este cambio tiene como objetivo optimizar la transición y el rendimiento en versiones futuras de Angular, ya que se prevé que el Angular Compatibility Compiler sea descontinuado en una versión posterior a la 12.2. Al usar el Angular Linker, las aplicaciones Angular solo podrán emplear bibliotecas compiladas parcialmente con Ivy, y no las bibliotecas previas al cambio.

Para entender cómo se implementa el Angular Compatibility Compiler, es necesario recordar que, en versiones anteriores de Angular 9, era necesario ejecutarlo manualmente antes de construir, probar o servir una aplicación Angular que empleara Ivy. Sin embargo, en versiones posteriores, el Angular CLI se encarga de invocar el ngcc de manera automática cuando se necesita. A pesar de esta automatización, sigue siendo posible ejecutar el compilador manualmente, lo cual resulta útil para ajustar la velocidad de la compilación según las necesidades del proyecto.

El Angular Compatibility Compiler debe ejecutarse siempre que se realice alguna de las siguientes acciones: iniciar un servidor de desarrollo, ejecutar pruebas automatizadas o compilar la aplicación. Cada vez que se instala una nueva versión de una biblioteca Angular o una nueva biblioteca de Angular desde un paquete, será necesario ejecutar nuevamente el compilador. Para optimizar este proceso, se recomienda integrar el ngcc como parte del hook postinstall de tu repositorio Git, de modo que la compilación se realice automáticamente sin necesidad de esperar.

Existen varias opciones al ejecutar el Angular Compatibility Compiler, cada una de ellas con un propósito específico. Algunas de las más relevantes son:

  • --create-ivy-entry-points: Crea un subdirectorio denominado _ivy_ngcc dentro de cada directorio de paquete de la biblioteca Angular, y coloca dentro de él los archivos Ivy compilados.

  • --first-only: En combinación con --properties, compila solo el primer formato de módulo reconocido en el paquete de la biblioteca, según el orden de las propiedades especificadas.

  • --properties: Permite especificar los formatos de paquete que se deben compilar utilizando el Angular Compatibility Compiler, por ejemplo, es2015, browser, module, etc.

  • --target: Esta opción permite compilar un paquete específico, como un componente o módulo de la aplicación Angular.

  • --tsconfig: Se usa para indicar un archivo tsconfig específico, que define los proyectos a compilar dentro del espacio de trabajo de Angular.

  • --use-program-dependencies: Permite decidir qué bibliotecas se compilarán en función del código fuente presente en el proyecto Angular.

El uso adecuado de estas opciones depende del contexto de desarrollo y las versiones específicas de Angular que estés utilizando. Si trabajas con Angular 9.0 o 11.1, podría ser recomendable omitir la opción --create-ivy-entry-points, ya que la compilación en el lugar de los archivos existentes ha demostrado ser ligeramente más rápida en estas versiones. Además, la opción --use-program-dependencies puede ser especialmente útil cuando se trabaja con Angular CDK y Angular Material, ya que permiten reducir significativamente el tiempo de compilación al compilar únicamente los paquetes utilizados por la aplicación, en lugar de compilar todos los sub-paquetes de forma predeterminada.

Cuando se ejecuta el Angular Compatibility Compiler manualmente, es importante seguir ciertas prácticas para optimizar la velocidad de compilación. Por ejemplo, al compilar el espacio de trabajo completo, puedes emplear el siguiente comando, que optimiza el tiempo de compilación y la compatibilidad con Ivy:

bash
ngcc --first-only --properties es2015 module fesm2015 esm2015 browser main --create-ivy-entry-points

Esto compilará solo un formato de paquete a la vez, siguiendo el orden preferido de formatos más rápidos de compilar. El uso de --create-ivy-entry-points es generalmente más rápido que reemplazar los paquetes existentes, y puede ser una opción más eficiente si se busca una optimización general.

Es esencial comprender que el Angular Compatibility Compiler es una herramienta crítica en proyectos que todavía emplean bibliotecas compiladas con View Engine. Sin embargo, la transición a Ivy es inminente y se recomienda comenzar a utilizar el Angular Linker en lugar del ngcc en futuras versiones de Angular.

Para optimizar el proceso en entornos de CI/CD (integración continua y entrega continua), se recomienda el uso de caché en el gestor de paquetes, ya que la restauración de todo el directorio node_modules puede ser demasiado lenta. De ser posible, se debe configurar el sistema para ejecutar el Angular Compatibility Compiler en cada ejecución del flujo de trabajo CI/CD desde cero, usando las opciones más rápidas disponibles.

En resumen, el uso del Angular Compatibility Compiler y el Angular Linker es indispensable para mantener la compatibilidad con versiones previas de Angular mientras se transita a Ivy, lo cual es clave para la optimización del rendimiento y la velocidad de compilación en proyectos Angular. Cada uno de los parámetros descritos en este capítulo puede marcar una diferencia significativa en el tiempo de construcción, haciendo que el flujo de trabajo sea más eficiente y menos propenso a errores.

¿Cómo manejar las limitaciones del compilador ahead-of-time en Angular?

El uso del compilador ahead-of-time (AOT) en Angular trae consigo varias ventajas en cuanto a la optimización del rendimiento de las aplicaciones, pero también impone ciertas restricciones a la forma en que gestionamos el código, especialmente en lo que respecta a la inicialización de variables y el uso de dependencias asíncronas. Es crucial entender cómo superar estas limitaciones para aprovechar al máximo las capacidades del compilador AOT.

Uno de los aspectos más importantes cuando se trabaja con Angular y el AOT es cómo manejamos las plantillas de los componentes. Al utilizar template literals etiquetados, como se puede observar en muchos ejemplos, no siempre se puede depender de estos en los metadatos de las plantillas de los componentes debido a las limitaciones del compilador AOT. Sin embargo, pueden ser útiles en las propiedades de la interfaz de usuario que se usan en los enlaces de plantilla, como se muestra en el siguiente ejemplo:

typescript
import { Component } from '@angular/core';
const subject = 'World'; @Component({ selector: 'app-root', template: ` {{ greeting }} `, }) export class AppComponent { greeting = String.raw`Hello, ${subject}!`; }

Este tipo de solución es válida, ya que garantiza que la inicialización de la variable greeting se haga antes de que el compilador AOT procese los metadatos del componente.

Por otro lado, la inicialización de las variables dentro de los metadatos también debe realizarse de manera inmediata para que sea compatible con el AOT. En el siguiente ejemplo, vemos un caso que no funcionará debido a que la inicialización de la variable greeting ocurre de manera asincrónica:

typescript
import { Component } from '@angular/core';
let greeting: string; setTimeout(() => { greeting = 'Hello, World!'; }, 0); @Component({ selector: 'app-hello', template: greeting, }) export class HelloComponent {}

Aquí, el compilador AOT no puede procesar la plantilla correctamente porque la variable greeting no está definida en el momento en que el compilador necesita los metadatos. Es importante notar que incluso si la variable greeting se inicializa de manera sincrónica en el código, pero se encuentra fuera del contexto del decorador, la solución seguirá siendo incorrecta:

typescript
import { Component } from '@angular/core';
let greeting: string; greeting = 'Hello, World!'; @Component({ selector: 'app-hello', template: greeting, }) export class HelloComponent {}

La razón detrás de este fallo es que, en el contexto del compilador AOT, las variables deben definirse e inicializarse en el mismo bloque de código para garantizar su correcta utilización.

Un ejemplo que sí cumple con los requisitos del compilador AOT es este:

typescript
import { Component } from '@angular/core'; let greeting = 'Hello, World!'; @Component({ selector: 'app-hello', template: greeting, }) export class HelloComponent {}

Aquí, la variable greeting está definida e inicializada simultáneamente, lo que permite que el compilador AOT procese la plantilla sin problemas. Si modificamos el valor de la variable después de su inicialización, es importante notar que en el contexto del compilador AOT el valor inicial será el que prevalezca:

typescript
import { Component } from '@angular/core'; let greeting = 'Hello, World!'; greeting = 'Hello, JIT compiler!'; @Component({ selector: 'app-hello', template: greeting, }) export class HelloComponent {}

Al utilizar el compilador AOT, el valor que se verá reflejado será 'Hello, World!', mientras que en el compilador JIT se mostrará 'Hello, JIT compiler!'. Este comportamiento se debe a las restricciones impuestas por el AOT sobre cómo se gestionan las variables en los metadatos de los componentes.

Cuando se trabaja con dependencias asíncronas, el desafío aumenta, ya que todo valor derivado de una dependencia asíncrona también debe resolverse de manera asíncrona. Esto plantea un problema a la hora de iniciar la aplicación, ya que el bootstrapping debe esperar a que las dependencias asíncronas se resuelvan primero.

Para resolver este problema, una técnica es usar un proveedor estático de plataforma. Este enfoque retrasa el bootstrapping de la aplicación hasta que la dependencia asíncrona esté disponible. Un ejemplo clásico de esto es cuando se cargan los feature flags de un archivo JSON de manera asíncrona:

typescript
export function loadFeatureFlags(): Promise<{ [feature: string]: boolean }> { return fetch('/assets/features.json') .then((response) => response.json()); }

En este caso, los feature flags se cargan antes de iniciar la aplicación, y luego se inyectan como una dependencia estática dentro del módulo de la aplicación. Este patrón es muy útil cuando tenemos configuraciones que pueden cambiar después de la compilación de la aplicación.

Otra técnica que se puede utilizar es la inicialización de dependencias asíncronas a través de un inicializador de la aplicación. Este inicializador se resuelve antes de que el componente raíz sea bootstrapado, permitiendo configurar el estado inicial de la aplicación que no depende de otros inicializadores. Un ejemplo de esto podría ser la configuración de los feature flags utilizando un servicio:

typescript
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root', }) export class FeatureFlagService { #featureFlags = new Map(); configureFeatures(featureFlags: { [feature: string]: boolean }): void { Object.entries(featureFlags).forEach(([feature, state]) => this.#featureFlags.set(feature, state) ); }
isEnabled(feature: string): boolean {
return this.#featureFlags.get(feature) ?? false; } }

En este caso, el inicializador de la aplicación carga los feature flags usando el servicio HttpClient antes de configurar los feature flags en el servicio FeatureFlagService. Esta técnica es ideal para manejar configuraciones que son esenciales para el inicio de la aplicación.

Además, estas técnicas no se limitan a la configuración de feature flags. Son igualmente útiles para cualquier tipo de configuración o dependencia que necesite ser resuelta antes de que la aplicación se inicie. En particular, cuando múltiples inicializadores de la aplicación requieren una dependencia compartida, esta metodología resulta ser la más adecuada para garantizar un inicio fluido y eficiente de la aplicación Angular.