Angular

Angular

Tutorial Angular: Effects en Angular

Aprenda a manejar efectos secundarios de forma reactiva utilizando los Effects. Guía detallada sobre su creación, uso y control del ciclo de vida.

¿Qué son los Effects?

Los Effects en Angular son una característica introducida en la versión 16 que permite ejecutar código de forma reactiva en respuesta a cambios en signals u otras fuentes de datos observables. Forman parte del nuevo sistema de reactividad de Angular y están diseñados para manejar efectos secundarios de manera declarativa y eficiente.

Un Effect es una función que se ejecuta automáticamente cuando sus dependencias cambian, pero a diferencia de los computeds, no produce un nuevo valor. En su lugar, realiza operaciones que tienen efectos secundarios, como actualizar el DOM, realizar llamadas a APIs, o interactuar con servicios del navegador.

Una característica importante de los Effects es que se ejecutan de forma asíncrona durante el proceso de detección de cambios. Esto significa que Angular agrupa múltiples cambios y ejecuta los efectos en el próximo microtask, lo que puede mejorar el rendimiento al evitar ejecuciones innecesarias.

Crear y usar un Effect

Los Effects se definen utilizando la función effect() proporcionada por Angular. Esta función toma como argumento una función de efecto que contiene el código a ejecutar. El sistema de reactividad de Angular rastrea automáticamente las señales y observables utilizados dentro de la función de efecto, y vuelve a ejecutar el efecto cuando cualquiera de estas dependencias cambia.

Para crear y usar un Effect en Angular, primero debemos importar la función effect del paquete @angular/core. Luego, podemos definir un Effect dentro de un componente o servicio de la siguiente manera:

import { Component, effect, signal } from '@angular/core';

@Component({
  selector: 'app-example',
  template: '<p>{{ message() }}</p>'
})
export class ExampleComponent {
  message = signal('Hola');

  constructor() {
    effect(() => {
      console.log(`El mensaje ha cambiado a: ${this.message()}`);
    });
  }

  updateMessage() {
    this.message.set('Hola Mundo');
  }
}

En este ejemplo, hemos creado un Effect que se ejecutará cada vez que el valor de message cambie. El Effect simplemente registra el nuevo valor en la consola.

Los Effects se ejecutan automáticamente una vez cuando se crean, y luego cada vez que cualquiera de sus dependencias (en este caso, message) cambia. Es importante notar que los Effects no devuelven un valor, sino que realizan acciones secundarias.

Podemos crear Effects más complejos que dependan de múltiples señales o que realicen operaciones más elaboradas:

import { Component, effect, signal, inject } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-user',
  standalone: true,
  imports: [JsonPipe],
  template: '<p>{{ userData() | json }}</p>'
})
export class UserComponent {
  userId = signal(1);
  userData = signal<any>(null);
  private http = inject(HttpClient);

  constructor() {
    effect(() => {
      this.http.get(`https://jsonplaceholder.typicode.com/users/${this.userId()}`)
        .subscribe(data => this.userData.set(data));
    });
  }

  updateUser(id: number) {
    this.userId.set(id);
  }
}

En este caso, el Effect se encarga de realizar una petición HTTP cada vez que userId cambia, actualizando userData con la respuesta.

También es posible pasar opciones al crear un Effect. Por ejemplo, podemos usar la opción allowSignalWrites para permitir que el Effect modifique señales:

effect(() => {
  const newValue = someComputation(this.inputSignal());
  this.outputSignal.set(newValue);
}, { allowSignalWrites: true });

Es importante tener en cuenta que los Effects se ejecutan de forma asíncrona por defecto. Si necesitamos que un Effect se ejecute de forma síncrona, podemos usar la opción manualCleanup:

effect(() => {
  // Este efecto se ejecutará de forma síncrona
  document.title = `User: ${this.userName()}`;
}, { manualCleanup: true });

Finalmente, los Effects se destruyen automáticamente cuando el componente o servicio que los contiene es destruido. Sin embargo, si necesitamos destruir un Effect manualmente, podemos hacerlo de la siguiente manera:

const cleanup = effect(() => {
  // Lógica del efecto
});

// Más tarde, cuando queramos destruir el efecto:
cleanup();

Al crear y usar Effects, es importante considerar su impacto en el rendimiento y asegurarse de que se utilizan de manera apropiada para manejar efectos secundarios en nuestra aplicación Angular.

¿En qué casos se utiliza un Effect?

Los Effects en Angular son una herramienta poderosa para manejar efectos secundarios de manera reactiva, pero es importante utilizarlos en los casos apropiados. Algunos escenarios comunes donde los Effects son particularmente útiles incluyen:

  1. Sincronización con APIs externas: Cuando necesitas realizar llamadas a servicios externos en respuesta a cambios en el estado de la aplicación. Por ejemplo, enviar datos actualizados a un servidor cada vez que un formulario cambia.
  2. Persistencia de datos: Para guardar automáticamente el estado de la aplicación en el almacenamiento local o en una base de datos cuando ciertos valores cambian.
  3. Manipulación del DOM: Cuando necesitas realizar cambios en el DOM que no pueden manejarse fácilmente a través del sistema de templates de Angular. Por ejemplo, modificar atributos de elementos HTML o interactuar con bibliotecas de terceros que manipulan el DOM directamente.
  4. Navegación programática: Para implementar lógica de navegación compleja basada en cambios en el estado de la aplicación, como redirigir a un usuario después de que se complete una acción específica.
  5. Logging y analytics: Para registrar eventos o enviar datos de análisis en respuesta a cambios específicos en el estado de la aplicación.
  6. Gestión de suscripciones: Para manejar suscripciones a observables de manera reactiva, iniciando o cancelando suscripciones basadas en cambios en el estado.
  7. Actualización de metadatos de la aplicación: Por ejemplo, cambiar dinámicamente el título de la página o las metaetiquetas basándose en la ruta actual o el contenido visualizado.
  8. Caching y prefetching: Para implementar estrategias de caché o precarga de datos basadas en el comportamiento del usuario o cambios en el estado de la aplicación.
  9. Integración con APIs del navegador: Cuando necesitas interactuar con APIs del navegador como Geolocation, Web Storage, o Notifications en respuesta a cambios en el estado de la aplicación.
  10. Manejo de temporizadores y animaciones: Para iniciar, detener o modificar temporizadores o animaciones basándose en cambios en el estado de la aplicación.

Cuando no usar Effects

Se debe evitar el uso de Effects para la propagación de cambios de estado. Esto puede resultar en errores de ExpressionChangedAfterItHasBeenChecked, actualizaciones circulares infinitas o ciclos innecesarios de detección de cambios. Estos problemas pueden afectar seriamente el rendimiento y la estabilidad de la aplicación.

Por ejemplo, no deberías usar Effects para actualizar un signal basado en el valor de otro signal. En su lugar, se recomienda usar computed signals para modelar estado que depende de otro estado. Esto permite a Angular manejar las actualizaciones de manera más eficiente y evita los problemas mencionados anteriormente.

Contexto de inyección para poder usar Effects

Para utilizar Effects en Angular, es crucial entender el contexto de inyección en el que operan. Los Effects están estrechamente ligados al sistema de inyección de dependencias de Angular y al ciclo de vida de los componentes y servicios.

Los Effects se pueden crear en cualquier lugar donde tengamos acceso al contexto de inyección de Angular. Esto incluye componentes, directivas y servicios. Sin embargo, el lugar donde creamos un Effect determina su ciclo de vida y su alcance.

En componentes y directivas, los Effects se crean típicamente en el constructor o en el método ngOnInit. Estos Effects estarán vinculados al ciclo de vida del componente o directiva, y se destruirán automáticamente cuando el componente o directiva sea destruido. 

Por ejemplo:

@Component({
  selector: 'app-example',
  template: '...'
})
export class ExampleComponent implements OnInit {
  constructor() {
    effect(() => {
      // Este Effect está vinculado al ciclo de vida del componente
    });
  }

  ngOnInit() {
    effect(() => {
      // Este Effect también está vinculado al ciclo de vida del componente
    });
  }
}

En servicios, los Effects pueden tener un alcance más amplio, dependiendo del alcance del servicio. Si el servicio es proporcionado a nivel de root, los Effects creados en él existirán durante toda la vida de la aplicación. Si el servicio está proporcionado a nivel de módulo o componente, los Effects seguirán el ciclo de vida de ese módulo o componente.

@Injectable({
  providedIn: 'root'
})
export class GlobalService {
  constructor() {
    effect(() => {
      // Este Effect existirá durante toda la vida de la aplicación
    });
  }
}

Es importante tener en cuenta que los Effects necesitan acceso al contexto de inyección de Angular para funcionar correctamente. Esto significa que no podemos crear Effects fuera de las clases de Angular o en funciones estáticas.

Para casos especiales donde necesitamos crear Effects fuera del contexto normal de inyección, Angular proporciona la función inject(). Esta función nos permite obtener el contexto de inyección actual y crear Effects de manera segura:

import { effect, inject } from '@angular/core';

function createCustomEffect() {
  // Obtenemos el contexto de inyección actual
  const injector = inject(Injector);

  // Creamos el Effect utilizando el contexto de inyección
  return effect(() => {
    // Lógica del Effect
  });
}

Para crear un Effect fuera del constructor, se puede pasar un Injector a effect via sus opciones:

@Component({...})
export class EffectiveCounterComponent {
  readonly count = signal(0);
  constructor(private injector: Injector) {}
  initializeLogging(): void {
    effect(() => {
      console.log(`The count is: ${this.count()}`);
    }, {injector: this.injector});
  }
}

Este enfoque es útil cuando necesitas crear Effects en métodos que se llaman después de la inicialización del componente o en situaciones donde no tienes acceso directo al contexto de inyección.

Cuando trabajamos con Effects en pruebas unitarias, es importante tener en cuenta el contexto de inyección. Podemos usar TestBed para configurar el entorno de pruebas y proporcionar los servicios necesarios:

import { TestBed } from '@angular/core/testing';
import { effect } from '@angular/core';

describe('EffectTest', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      // Configuración del módulo de pruebas
    });
  });

  it('should create an effect', () => {
    const component = TestBed.createComponent(MyComponent).componentInstance;
    expect(() => effect(() => {})).not.toThrow();
  });
});

Entender el contexto de inyección para los Effects nos permite utilizarlos de manera efectiva y segura en nuestras aplicaciones Angular, asegurándonos de que se creen y destruyan correctamente según el ciclo de vida de los componentes y servicios en los que se utilizan.

Destruir Effects

Los Effects en Angular se destruyen automáticamente cuando el componente o servicio que los contiene es destruido. Sin embargo, en algunas situaciones, puede ser necesario destruir un Effect manualmente antes de que su contexto de inyección sea destruido. Esto es particularmente útil cuando se necesita un control más granular sobre el ciclo de vida de los Effects o cuando se crean Effects dinámicamente.

Para destruir un Effect manualmente, podemos utilizar la función de limpieza que devuelve la función effect(). Esta función de limpieza, cuando se invoca, detiene la ejecución del Effect y lo desvincula de sus dependencias. 

Aquí tienes un ejemplo de cómo crear y destruir un Effect manualmente:

import { Component, effect, EffectRef, inject, Injector, runInInjectionContext, signal } from '@angular/core';

@Component({
  selector: 'app-example',
  template: '    
<button (click)="toggleEffect()">{{ effectActive() ? 'Destroy' : 'Create' }} Effect</button>
<button (click)="incrementCounter()">Increment Counter ({{ counter() }})</button>
    '
})
export class ExampleComponent {
 counter = signal(0);
  effectActive = signal(false);
  private effectRef: EffectRef | null = null;
  private injector = inject(Injector);

  toggleEffect() {
    if (this.effectActive()) {
      this.destroyEffect();
    } else {
      this.createEffect();
    }
    this.effectActive.update(active => !active);
  }

  private createEffect() {
    runInInjectionContext(this.injector, () => {
      this.effectRef = effect(() => {
        console.log(`Counter value: ${this.counter()}`);
      });
    });
    console.log('Effect created');
  }

  private destroyEffect() {
    if (this.effectRef) {
      this.effectRef.destroy();
      this.effectRef = null;
      console.log('Effect destroyed');
    }
  }

  incrementCounter() {
    this.counter.update(value => value + 1);
  }

  ngOnDestroy() {
    this.destroyEffect();
  }
}

Este ejemplo demuestra cómo:

  • Crear manualmente un efecto cuando se hace clic en el botón "Create Effect".
  • Destruir manualmente el efecto cuando se hace clic en el botón "Destroy Effect".
  • El efecto registra el valor del contador cada vez que cambia, pero solo cuando el efecto está activo.
  • El efecto se limpia adecuadamente cuando el componente se destruye.

Es importante tener en cuenta que la destrucción manual de efectos debe usarse con cuidado, ya que puede llevar a comportamientos inesperados si no se maneja correctamente. En la mayoría de los casos, es preferible dejar que Angular maneje el ciclo de vida de los efectos automáticamente.

Para Effects que necesitan ser creados y destruidos dinámicamente, puedes mantener una colección de funciones de limpieza:

private effectCleanups: (() => void)[] = [];

createDynamicEffect() {
  const cleanup = effect(() => {
    // Lógica del Effect
  });
  this.effectCleanups.push(cleanup);
}

destroyAllEffects() {
  this.effectCleanups.forEach(cleanup => cleanup());
  this.effectCleanups = [];
}

ngOnDestroy() {
  this.destroyAllEffects();
}

Este enfoque te permite crear múltiples Effects dinámicamente y asegurarte de que todos se destruyan adecuadamente cuando sea necesario.

Recuerda que la destrucción manual de Effects es una operación avanzada y, en la mayoría de los casos, permitir que Angular maneje automáticamente el ciclo de vida de los Effects es suficiente y más seguro. Solo deberías considerar la destrucción manual cuando tengas requisitos específicos que no puedan ser satisfechos por el comportamiento predeterminado de Angular.

Se puede usar la opción manualCleanup para crear Effects que duran hasta que se destruyen manualmente, pero se debe tener cuidado de limpiarlos cuando ya no sean necesarios.

Certifícate en Angular con CertiDevs PLUS

Ejercicios de esta lección Effects en Angular

Evalúa tus conocimientos de esta lección Effects en Angular con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Signals en Angular

Angular
Puzzle

Guards funcionales

Angular
Test

Decodificar JWT en Angular

Angular
Test

Servicio con HttpClient

Angular
Código

Ciclo de vida de componentes en Angular

Angular
Test

Gestión de productos de Fake Store API

Angular
Proyecto

Data binding en Angular

Angular
Test

Routes sin módulos en Angular

Angular
Código

Router en Angular

Angular
Test

Instalación de Angular

Angular
Puzzle

Route Guards basados en interfaces

Angular
Código

La directiva @if en Angular

Angular
Puzzle

Formularios reactivos en Angular

Angular
Código

Servicios en Angular

Angular
Puzzle

Interceptor funcional

Angular
Test

Servicio con Array

Angular
Código

La directiva @for en Angular

Angular
Puzzle

Interceptores HTTP

Angular
Código

Componentes standalone true

Angular
Puzzle

Formularios con ngModel en Angular

Angular
Puzzle

Routes en Angular

Angular
Test

Comunicación entre componentes Angular

Angular
Test

Parámetros en rutas con ActivatedRoute

Angular
Test

CRUD de Restaurantes y Platos

Angular
Proyecto

Tablas en Angular Material

Angular
Puzzle

Formulario de registro de usuarios

Angular
Proyecto

Instalación y uso de NgBoostrap

Angular
Puzzle

Desarrollo de componentes Angular

Angular
Test

JWT en Angular

Angular
Código

Formularios reactivos en Angular

Angular
Puzzle

Formularios en Angular Material

Angular
Puzzle

Layout con Angular Material

Angular
Puzzle

Effects en Angular

Angular
Test

Data binding

Angular
Código

HttpClient en servicios de Angular

Angular
Puzzle

Desarrollo de módulos Angular

Angular
Puzzle

Comandos Angular CLI

Angular
Puzzle

Subir archivo en formularios

Angular
Test

La directiva routerLink en Angular

Angular
Test

Todas las lecciones de Angular

Accede a todas las lecciones de Angular y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Instalación Angular

Angular

Introducción Y Entorno

Comandos Angular Cli

Angular

Introducción Y Entorno

Desarrollo De Componentes Angular

Angular

Componentes

Data Binding En Angular

Angular

Componentes

Ciclo De Vida De Componentes En Angular

Angular

Componentes

Comunicación Entre Componentes Angular

Angular

Componentes

La Directiva @If En Angular

Angular

Componentes

La Directiva @For En Angular

Angular

Componentes

Componentes Standalone

Angular

Componentes

Desarrollo De Módulos Angular

Angular

Módulos

Routes En Angular

Angular

Enrutado Y Navegación

Router En Angular

Angular

Enrutado Y Navegación

La Directiva Routerlink En Angular

Angular

Enrutado Y Navegación

Parámetros En Rutas Con Activatedroute

Angular

Enrutado Y Navegación

Routes Sin Módulos En Angular

Angular

Enrutado Y Navegación

Servicios En Angular

Angular

Servicios E Inyección De Dependencias

Httpclient En Servicios De Angular

Angular

Servicios E Inyección De Dependencias

Formularios Con Ngmodel En Angular

Angular

Formularios

Formularios Reactivos En Angular

Angular

Formularios

Subir Archivo En Formularios

Angular

Formularios

Layout Con Angular Material

Angular

Integración Con Angular Material

Tablas En Angular Material

Angular

Integración Con Angular Material

Formularios En Angular Material

Angular

Integración Con Angular Material

Instalación Y Uso De Ngboostrap

Angular

Integración Con Bootstrap Css

Signals En Angular

Angular

Signals Y Reactividad

Effects En Angular

Angular

Signals Y Reactividad

Route Guards Basados En Interfaces

Angular

Autenticación Y Autorización

Guards Funcionales

Angular

Autenticación Y Autorización

Interceptores Http Basados En Interfaz

Angular

Autenticación Y Autorización

Interceptores Http Funcionales

Angular

Autenticación Y Autorización

Seguridad Jwt En Angular

Angular

Autenticación Y Autorización

Decodificar Tokens Jwt En Angular

Angular

Autenticación Y Autorización

Certificados de superación de Angular

Supera todos los ejercicios de programación del curso de Angular y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender qué son los Effects en Angular.

  • Aprender a crear y usar effects en componentes y servicios.

  • Controlar el ciclo de vida de los effects, incluyendo su destrucción manual.

  • Utilizar el contexto de inyección para trabajar con effects.