🇺🇸 EN
Frontend
·4 min read·↗·🇺🇸 EN

Cómo usar NgRx Selectors en Angular

Portada principal del artículo: Cómo usar NgRx Selectors en Angular

En NgRx, cuando queremos obtener datos del store, la forma más rápida y directa suele ser usar store.select. Nos permite consultar cualquier porción (slice) del estado. Sí, suena curioso, pero cualquier porción nos devuelve un Observable<any>.

Por ejemplo:

Esto es flexible, pero también peligroso: ¿qué pasa si la estructura de nuestro state cambia? La mejor solución para evitar dolores de cabeza es usar Selectors. ¡Vamos a ver cómo funcionan!

NgRx Selectors

Los selectores nos ayudan a obtener partes del estado del store con total seguridad de tipos (type safety). Funcionan como un espejo de nuestro estado, lo que facilita consultarlo en cualquier parte y nos ayuda a no duplicar código en la aplicación.

NgRx nos ofrece dos funciones principales para crearlos: createFeatureSelector() y createSelector().

La función createFeatureSelector nos permite definir una porción tipada de nuestro estado a partir de su interfaz o tipo:

export const selectHomeState = createFeatureSelector<HomeState>('home');

La función createSelector toma el featureState como primer parámetro y una función secundaria para leer ese estado y extraer la propiedad exacta que necesitamos:

export const selectLoading = createSelector(
  selectHomeState,
  (homeState) => homeState.loading
)

Ahora que ya conocemos las dos funciones básicas de NgRx, vamos a ponerlas en práctica en un proyecto real 🔥.

Creando selectores

Es momento de usar createFeatureSelector y createSelector en nuestro proyecto. Vamos a continuar con el repositorio del artículo inicial sobre NgRx: clónalo y cámbiate a la rama action-creators:

git clone https://github.com/danywalls/start-with-ngrx.git
git switch action-creators

Abre el proyecto en tu editor favorito y crea el archivo src/app/pages/about-me/state/home.selectors.ts. Ahora importa las funciones createFeatureSelector y createSelector. Usa createFeatureSelector junto con la interfaz HomeState para crear selectHomeState:

export const  selectHomeState = createFeatureSelector<HomeState>('home');

Después de eso, usa selectHomeState para crear los selectores de players, loading y acceptTerms:

export const selectLoading = createSelector(
  selectHomeState,
  (homeState) => homeState.loading
)
 
export const selectPlayers = createSelector(
  selectHomeState,
  (homeState) => homeState.players
)
export const selectAcceptTerms = createSelector(
  selectHomeState,
  (homeState) => homeState.acceptTerms,
)

También podemos componer selectores. Por ejemplo, si queremos saber cuándo players tiene datos y el usuario ya aceptó los términos (acceptTerms), podemos crear selectAllTaskDone. Este selector combina selectPlayers y selectAcceptTerms para verificar que ambas condiciones se cumplan:

export const selectAllTaskDone = createSelector(
  selectPlayers,
  selectAcceptTerms,
  (players, acceptTerms) => {
    return acceptTerms && players.length > 0;
  }
)

El código completo de home.selectors.ts queda de la siguiente manera:

import {createFeatureSelector, createSelector} from "@ngrx/store";
import {HomeState} from "./home.state";
 
 
export const  selectHomeState = createFeatureSelector<HomeState>('home');
 
export const selectLoading = createSelector(
  selectHomeState,
  (homeState) => homeState.loading
)
 
export const selectPlayers = createSelector(
  selectHomeState,
  (homeState) => homeState.players
)
export const selectAcceptTerms = createSelector(
  selectHomeState,
  (homeState) => homeState.acceptTerms,
)
 
export const selectAllTaskDone = createSelector(
  selectPlayers,
  selectAcceptTerms,
  (players, acceptTerms) => {
    return acceptTerms && players.length > 0;
  }
)

Muy bien, con los selectores listos, es momento de refactorizar home.component.ts para empezar a usarlos. Importa cada selector desde home.selectors.ts.

Nota: Elimina la función toSignal y utiliza store.selectSignal para transformar automáticamente los observables de los selectores en signals.

  public $loading = this._store.selectSignal(selectLoading);
  public $players = this._store.selectSignal(selectPlayers);
  public $acceptTerms = this._store.selectSignal(selectAcceptTerms);

Por último, crea una variable nueva para usar el selector compuesto selectAllTaskDone:

  public $allTasksDone = this._store.selectSignal(selectAllTaskDone);

Actualiza el template de home.component.html para usar la signal $allTasksDone:

@if (!$allTasksDone()) {
  Wait for the players and  accept terms
 
} @else {
  <h2>Everything done!🥳</h2>
}

Guarda los cambios y todo seguirá funcionando sin problemas 😄. Para probar los selectores compuestos: cuando se dispare playersLoadSuccess y hagas clic en acceptTerms, verás el mensaje "Everything done!".

Conclusión

En este artículo aprendimos cómo usar selectors para consultar y gestionar porciones de estado en NgRx en lugar de depender directamente de store.select. Con createFeatureSelector y createSelector ganamos tipado seguro, memoización y la capacidad de componer lógica reutilizable de forma limpia.

Parte de la serie Frontend

Modern frontend development — Angular, React, TypeScript, accessibility, and performance.

Ver serie completa

Preguntas Frecuentes

¿Cuál es la diferencia entre createFeatureSelector y createSelector en NgRx?

createFeatureSelector crea un selector tipado de nivel superior que apunta a una porción (feature slice) específica del estado global del store. createSelector toma uno o más de estos selectores como proyectores de entrada y deriva porciones más específicas a partir de ellos. Juntos te permiten componer selectores de forma mantenible y fácil de probar, sin dispersar el acceso directo al estado por todos tus componentes.

¿Por qué debería usar selectores de NgRx en lugar de acceder al estado directamente con store.select?

Usar store.select directamente con una ruta en string o una función anónima devuelve un Observable de tipo any, lo que elimina el tipado seguro y puede fallar silenciosamente si cambia la estructura del estado. Los selectores creados con createFeatureSelector y createSelector tienen tipado completo, cuentan con memoización para recalcularse solo cuando sus entradas cambian, y se pueden reutilizar en múltiples componentes, reduciendo la duplicación y mejorando el rendimiento.

¿Cómo funciona la memoización en los selectores de NgRx y por qué mejora el rendimiento?

Los selectores de NgRx tienen memoización por defecto, lo que significa que guardan en caché su último resultado calculado y solo vuelven a calcularlo cuando uno de sus selectores de entrada emite una nueva referencia. Esto evita cálculos innecesarios y previene disparar la detección de cambios en los componentes de Angular cuando el valor del estado no ha cambiado realmente, algo muy valioso en aplicaciones grandes con estado derivado complejo.

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.