You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NGRX中统一管理文章列表并按ID加载单篇文章的可行性问询

Is Using a Unified Article List in NGRX Store (Filtered by ID) a Reasonable Approach?

Absolutely, this approach is not just reasonable—it’s a best practice for NGRX state management, especially as your application scales. Moving from individual properties for each specific article to a centralized list (indexed by ID) solves the redundancy problem you’re facing and aligns with how NGRX is designed to handle collections of entities.

Here’s why this pattern works so well, plus practical examples to implement it:

Key Benefits of This Approach

  • Eliminates Redundant Code: Instead of writing duplicate actions (like LoadArticle1, LoadArticle2), effects, and reducer cases for each article, you’ll have a single set of logic that works for any article ID. This makes your codebase cleaner and easier to maintain.
  • Scalable State Shape: If you ever need to add more articles to your page (or other parts of the app), you won’t have to modify your state structure, actions, or reducers—just dispatch the same action with a new ID.
  • Efficient Data Access: Indexing articles by ID (using a dictionary/object) lets you look up specific articles in O(1) time, which is more efficient than filtering an array every time you need a single item.

Practical Implementation Example

1. Define the State Structure

Use an entity-based state to store articles, along with loading/error states tracked per ID:

interface Article {
  id: string;
  title: string;
  content: string;
  // other article properties
}

interface ArticlesState {
  entities: { [id: string]: Article }; // Index articles by ID for quick lookup
  ids: string[]; // Optional: Maintain order if you need to render articles in sequence
  loading: { [id: string]: boolean }; // Track loading state per article
  error: { [id: string]: string | null }; // Track errors per article
}

const initialState: ArticlesState = {
  entities: {},
  ids: [],
  loading: {},
  error: {}
};

2. Create Reusable Actions

Replace your per-article actions with a single set of actions that accept an article ID:

import { createAction, props } from '@ngrx/store';

export const loadArticle = createAction(
  '[Articles] Load Article',
  props<{ id: string }>()
);

export const loadArticleSuccess = createAction(
  '[Articles] Load Article Success',
  props<{ article: Article }>()
);

export const loadArticleFailure = createAction(
  '[Articles] Load Article Failure',
  props<{ id: string; error: string }>()
);

3. Build a Single Reducer

Your reducer will handle all article-related state changes with generic logic, no per-article cases:

import { createReducer, on } from '@ngrx/store';
import * as ArticleActions from './article.actions';

export const articlesReducer = createReducer(
  initialState,
  on(ArticleActions.loadArticle, (state, { id }) => ({
    ...state,
    loading: { ...state.loading, [id]: true },
    error: { ...state.error, [id]: null }
  })),
  on(ArticleActions.loadArticleSuccess, (state, { article }) => ({
    ...state,
    entities: { ...state.entities, [article.id]: article },
    ids: state.ids.includes(article.id) ? state.ids : [...state.ids, article.id],
    loading: { ...state.loading, [article.id]: false }
  })),
  on(ArticleActions.loadArticleFailure, (state, { id, error }) => ({
    ...state,
    loading: { ...state.loading, [id]: false },
    error: { ...state.error, [id]: error }
  }))
);

4. Reuse Effects for Loading

Your effect will handle loading any article by ID, no need for duplicate effects:

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import * as ArticleActions from './article.actions';
import { ArticleService } from '../services/article.service';

@Injectable()
export class ArticleEffects {
  loadArticle$ = createEffect(() =>
    this.actions$.pipe(
      ofType(ArticleActions.loadArticle),
      mergeMap(({ id }) =>
        this.articleService.getArticle(id).pipe(
          map((article) => ArticleActions.loadArticleSuccess({ article })),
          catchError((error) => of(ArticleActions.loadArticleFailure({ id, error: error.message })))
        )
      )
    )
  );

  constructor(private actions$: Actions, private articleService: ArticleService) {}
}

5. Select Specific Articles in Your Smart Component

Use memoized selectors to fetch the two specific articles you need:

import { createSelector } from '@ngrx/store';
import { AppState } from '../app.state';
import { ArticlesState } from './article.state';

// Base selector for the articles state
const selectArticlesState = (state: AppState) => state.articles;

// Selector to get an article by ID
export const selectArticleById = (id: string) =>
  createSelector(selectArticlesState, (state: ArticlesState) => state.entities[id]);

// In your smart component
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Article } from './article.model';
import * as ArticleActions from './article.actions';
import { selectArticleById } from './article.selectors';

@Component({
  selector: 'app-article-page',
  templateUrl: './article-page.component.html'
})
export class ArticlePageComponent implements OnInit {
  article1$: Observable<Article | undefined>;
  article2$: Observable<Article | undefined>;

  constructor(private store: Store) {
    this.article1$ = this.store.select(selectArticleById('article-1-id'));
    this.article2$ = this.store.select(selectArticleById('article-2-id'));
  }

  ngOnInit() {
    // Dispatch actions to load the two articles
    this.store.dispatch(ArticleActions.loadArticle({ id: 'article-1-id' }));
    this.store.dispatch(ArticleActions.loadArticle({ id: 'article-2-id' }));
  }
}

Final Notes

Even if you only need two articles right now, this pattern future-proofs your code. If you later need to add more articles, or reuse article-loading logic elsewhere in the app, you won’t have to refactor your entire state setup. It also keeps your reducer logic focused on updating the entity collection, which aligns with NGRX’s core principles of single-responsibility reducers.

内容的提问来源于stack exchange,提问作者Ondra Piza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:18:37