Arquitectura en proyectos Angular

Actualmente en la documentación oficial de Angular hay 12 páginas largas para explicar cómo funcionan los NgModules, incluidas las preguntas frecuentes. Pero después de leer todo esto, puede ser que aun quedemos confundidos. Preguntas básicas como “¿dónde es un buen lugar para guardar un servicio?” No tienen una respuesta clara, incluso hay sugerencias a veces contradictorias.

Así que vamos a repensar todo e implementar una arquitectura decente para aplicaciones Angular , con estos objetivos en mente:

  • consistencia : simplicidad (para aplicaciones pequeñas) y escalabilidad (para aplicaciones grandes),
  • reutilización en diferentes proyectos,
  • optimización (consistente con o sin carga diferida),
  • capacidad de prueba .

Módulos Angular

¿Qué es un NgModule?

El propósito de un NgModule es solo agrupar componentes y / o servicios que se pertenecen . Ni más o menos.

Entonces puede comparar eso con un paquete Java o un espacio de nombres PHP / C #.

La única pregunta es: ¿cómo eliges agrupar cosas?

Tipos de módulos

Hay 3 tipos principales de NgModules que puedes hacer:

  • módulos de páginas,
  • módulos de servicios globales,
  • módulos de componentes reutilizables .

Los modulos de componente se usaran siempre (de lo contrario, su aplicación está vacía). Los otros 2 tipos de módulos son opcionales, pero llegarán pronto si desea reutilizar y optimizar su código.

Módulos de páginas

Los módulos de páginas son módulos con enrutamiento . Están aquí para separar y organizar las diferentes áreas de su aplicación . Se cargan solo una vez , ya sea en el AppModule o mediante la carga diferida .

Por ejemplo, podría tener un AccountModule para las páginas de registro, inicio de sesión y cierre de sesión; luego, un HeroesModule para la lista de héroes y páginas de detalles de héroes; y así.

Estos módulos contienen 3 cosas:

  • / shared : servicios e interfaces,
  • / pages : componentes enrutados,
  • / components : componentes de presentación puros.

Servicios compartidos

Para mostrar datos en una página, primero necesita datos. Aquí es donde intervienen los servicios.

@Injectable({ providedIn: 'root' })
export class SomeService {

  constructor(protected http: HttpClient) {}

  getData() {
    return this.http.get<SomeData>('/path/to/api');
  }
}

Pronto, varias páginas necesitarán el mismo servicio. Por lo tanto es recomendable agregar los servicios en el directorio Shared .

Pero asegúrese de que sus servicios para las páginas sean específicos del módulo , ya que si opta por  lazy-loading , solo estarán disponibles en este módulo particular (lo cual es bueno), y no en otra parte de la aplicación.

Retomemos un AccountModule como ejemplo. El servicio de cuenta solo debe administrar la comunicación con la API (que dice “sí” o “no” en función de las credenciales del usuario). El estado de conexión del usuario no se debe almacenar aquí, ya que puede no estar disponible en otra parte de la aplicación. Será administrado por un módulo de servicios globales (ver a continuación).

Páginas: componentes enrutados

Un componente de página simplemente inyecta el servicio y lo usa para obtener los datos.

Se podría mostrar los datos directamente en el componente de la plantilla pero debería no : los datos deben ser transferidos a otro componente a través de un atributo.

@Component({
  template: `<app-presentation *ngIf="data" [data]="data"></app-presentation>`
})
export class PageComponent {

  data: SomeData;

  constructor(protected someService: SomeService) {}

  ngOnInit() {
    this.someService.getData().subscribe((data) => {
      this.data = data;
    });
  }

}

Cada componente de página está asociado a una ruta .

Componentes de presentación

Un componente de presentación simplemente recupera los datos transferidos con el decorador de entrada y lo muestra en la plantilla.

@Component({
  selector: 'app-presentation',
  template: `<h1>{{data.title}}</h1>`
})
export class PresentationComponent {

  @Input() data: SomeData;

}

¿Es este MVx? (MVC, MVP, etc)

En un nivel teórico, no. Pero si vienes del mundo de back-end y te ayuda en un nivel práctico, puedes compararlo:

  • los servicios serían los Modelos,
  • los componentes de la presentación serían las Vistas,
  • los componentes de páginas serían los Controladores / Presentadores / Modelos de Vista (elija el que está acostumbrado).

Incluso no es exactamente el mismo concepto, el objetivo es el mismo: separación de preocupaciones . Y ¿Por qué es esto importante?

  • reutilización : los componentes de presentación se pueden reutilizar en diferentes páginas,
  • optimizabilidad : la detección de cambios de los componentes de la presentación se puede optimizar ,
  • capacidad de prueba: las pruebas unitarias son posibles en los componentes de presentación (simplemente olvídese de las pruebas si no separó las preocupaciones, será un desastre terrible).

Resumen

Ejemplo de un módulo de páginas:

@NgModule({
  imports: [CommonModule, MatCardModule, PagesRoutingModule],
  declarations: [PageComponent, PresentationComponent]
})
export class PagesModule {}

Módulos de servicios globales

Los módulos de servicios globales son módulos con servicios que necesita a través de toda la aplicación . Como los servicios tienen generalmente un alcance global , estos módulos se cargan solo una vez en el AppModule, y luego se puede acceder a los servicios en cualquier lugar (incluso en los módulos con carga diferida).

Ciertamente usa al menos uno: el módulo HttpClient . Y pronto necesitarás la tuya. Un caso muy común es un AuthModule para almacenar el estado de conexión del usuario (ya que esta información es necesaria en todas partes en la aplicación) y guardar el token.

Nota: desde Angular 6, ya no necesita un módulo para los servicios , ya que ellos mismos se proveen automáticamente. Pero no cambia la arquitectura descrita aquí.

Reusabilidad

Los módulos de servicios globales son reutilizables a través de diferentes proyectos si se cuida de no tener una dependencia específica en ellos (sin UI o código específico de la aplicación), y si se separan cada una en diferentes módulos ( no coloque cada servicio en un solo módulo global grande) )

Como dicho módulo se usará desde el exterior, debe hacer un punto de entrada , donde puede exportar el NgModule, los servicios y tal vez las interfaces y los tokens de inyección.

export { SomeService } from './some.service';
export { SomeModule } from './some.module';

¿Debo hacer un CoreModule?

No es necesario . La documentación sugiere hacer un CoreModule para servicios globales. Seguramente puede agruparlos en un directorio / core / , pero como se mencionó anteriormente, asegúrese de separar primero cada característica . Usted debe no poner todos los servicios globales en un solo CoreModule, de lo contrario no será capaz de volver a utilizar sólo una característica en otro proyecto.

Resumen

Ejemplo de un módulo de servicios globales:

@NgModule({
  providers: [SomeService]
})
export class SomeModule {}

De nuevo, el módulo no es necesario desde Angular 6.

Módulos de componentes re-utilizables

Los módulos de componentes re utilizables son módulos de componentes de UI que le gustaría reutilizar en diferentes proyectos. Como los componentes tienen un alcance local , estos módulos se importan en los módulos de cada página donde los necesita.

Ciertamente usa uno, como Material , NgBootstrap o PrimeNg . Tú también puedes hacer lo tuyo.

¿Cómo obtener los datos?

Los componentes de interfaz de usuario son componentes de presentación puros . Por lo tanto, funcionan exactamente igual que en los módulos de páginas (ver arriba): los datos deben venir del decorador de entrada (y a veces de <ng-content> en casos avanzados).

@Component({
  selector: 'ui-carousel'
})
export class CarouselComponent {

  @Input() delay = 5000;

}

Usted debe no confiar en un servicio, ya que los servicios son a menudo específicos para una aplicación en particular. ¿Por qué? Al menos debido a la API URL. Proporcionar los datos será el papel del componente de páginas. El componente UI solo recupera datos pasados ​​por otra persona.

Componentes públicos y privados

Como los componentes están en el ámbito local , no olvide exportarlos en el NgModule. Solo necesita exportar los públicos , los sub componentes internos pueden mantenerse privados.

Directivas y tuberías

Un módulo UI también puede ser sobre directivas o tuberías. Igual que los componentes: necesitan ser exportados si son públicos.

Servicios privados

Los servicios dentro de los módulos de UI pueden ser relevantes para la manipulación de datos si no contienen nada específico. Pero luego, asegúrese de proporcionarlos en el componente , para que tengan un alcance local / privado, y ciertamente no en el NgModule.

@Component({
  selector: 'some-ui',
  providers: [LocalService]
})
export class SomeUiComponent {}

Servicios públicos

Pero, ¿qué sucede si su módulo de UI también necesita proporcionar servicios públicos en relación con el componente? Se debe evitar tanto como sea posible, pero es relevante en algunos casos.

A continuación, proporcionará los servicios públicos en el NgModule. Pero como el módulo se cargará varias veces debido al alcance de los componentes, causará un problema para los servicios.

Luego necesita un código adicional para cada servicio público para evitar que se carguen varias veces . Sería demasiado largo para explicarlo aquí, pero es una mejor práctica (hecho en Material, por ejemplo). Simplemente reemplace SomeService por el nombre de su clase:

export function SOME_SERVICE_FACTORY(parentService: SomeService) {
  return parentService || new SomeService();
}

@NgModule({
  providers: [{
    provide: SomeService,
    deps: [[new Optional(), new SkipSelf(), SomeService]],
    useFactory: SOME_SERVICE_FACTORY
  }]
})
export class UiModule {}

Reusabilidad

Los módulos de los componentes de la interfaz de usuario son re-utilizables a través de diferentes proyectos. Como se usará desde el exterior, debe hacer un punto de entrada , donde puede exportar el NgModule, los componentes públicos / exportados (y tal vez directivas, tuberías, servicios públicos , interfaces y tokens de inyección).

export { SomeUiComponent }  from './some-ui/some-ui.component';
export { UiModule } from './ui.module';

¿Debo hacer un SharedModule?

No se . La documentación sugiere hacer un SharedModule, para factorizar todos los módulos de componentes dentro de un módulo. Pero iré en contra de la documentación de este.

El problema es que cada módulo en el que importa el SharedModule se vuelve específico para su aplicación y luego no será reutilizable en otro proyecto.

Es normal tener que importar dependencias cada vez que los necesite. Y con las herramientas actuales, como las importaciones automáticas en VS Code , ya no es una carga.

Pero seguramente puede agrupar sus módulos de componentes dentro de un directorio / ui / (no lo llame / compartido /, será confuso con los servicios que también se comparten).

Resumen

Ejemplo de un módulo de componentes de UI reutilizables:

@NgModule({
  imports: [CommonModule],
  declarations: [PublicComponent, PrivateComponent],
  exports: [PublicComponent]
})
export class UiModule {}

Conclusión

Si sigues esos pasos:

  • Tendrás una arquitectura consistente : en aplicaciones pequeñas o grandes, con o sin carga lenta,
  • sus módulos de servicios globales y sus módulos de componentes reutilizables están listos para ser empacados como bibliotecas , reutilizables en otros proyectos,
  • podrás hacer pruebas unitarias sin llorar .

Aquí hay un ejemplo de una arquitectura del mundo real:

app/
|- app.module.ts
|- app-routing.module.ts
|- core/
   |- auth/
      |- auth.module.ts (optional since Angular 6)
      |- auth.service.ts
      |- index.ts
   |- othermoduleofglobalservice/
|- ui/
   |- carousel/
      |- carousel.module.ts
      |- index.ts
      |- carousel/
         |- carousel.component.ts
         |- carousel.component.css
    |- othermoduleofreusablecomponents/
|- heroes/
   |- heroes.module.ts
   |- heroes-routing.module.ts
   |- shared/
      |- heroes.service.ts
      |- hero.ts
   |- pages/
      |- heroes/
         |- heroes.component.ts
         |- heroes.component.css
      |- hero/
         |- hero.component.ts
         |- hero.component.css
   |- components/
      |- heroes-list/
         |- heroes-list.component.ts
         |- heroes-list.component.css
      |- hero-details/
         |- hero-details.component.ts
         |- hero-details.component.css
|- othermoduleofpages/

El objetivo de este post es también confrontar esta arquitectura con la comunidad, es decir, usted que está leyendo. Entonces, si me perdí algunos casos de uso, siéntete libre de comentar.

Esta arquitectura base la pude aprender del siguiente articulo, es muy bueno, y si siguen sus otros post, podrán aprender mas sobre las base  de angular a profundidad.

Fuente: Architecture in Angular projects – Cyrille Tuzi – Medium

LEAVE YOUR COMMENTS