我目前正在学习ANGLING,并在学习马克西米安·施瓦茨米勒·乌德米课程.

我有一个简单的CRUD应用程序来管理食谱,数据保存在数据库中,我希望能够通过发送一个HTTP请求来删除、创建和更新食谱,然后相应地更新食谱列表.

UI of this application

仅使用RxJS BehaviorSubject和一个服务,我目前就有这个删除食谱的实现:

export class RecipeDetailComponent implements OnInit {
    selectedRecipe: Recipe;
  
    constructor(
      private recipeService: RecipeService,
      private activatedRoute: ActivatedRoute,
      private router: Router
    ) { }
  
    ngOnInit() {
      this.activatedRoute.params.subscribe((params) => {
        const id = params.id;
        this.recipeService.getRecipeById(id).subscribe((recipe) => {
          this.selectedRecipe = recipe;
        });
      });
    }
  
  
    onDeleteRecipe() {
      this.recipeService.deleteRecipe(this.selectedRecipe.id).subscribe({
        next: () => {
          this.router.navigate(['/recipes']);
          this.recipeService.recipesUpdate.next();
        },
        error: (error) => {
          console.error('error deleting recipe : ', error);
        }
      });
    }
  }

export class RecipeListComponent implements OnInit {
  recipes$: Observable<Recipe[]>;
  isLoading = false;
  errorMessage: string;
  constructor(private recipeService: RecipeService) {}

  ngOnInit() {
    this.initRecipes();
    this.recipeService.recipesUpdate.subscribe(() => this.initRecipes());
  }

  initRecipes() {
    this.isLoading = true;
    this.recipes$ = this.recipeService.getRecipes().pipe(
      catchError((error) => {
        console.error('error retrieving recipes : ', error);
        this.errorMessage = `Error retrieving recipes : ${error.error.error}`;
        return of([]);
      }),
      tap({ complete: () => (this.isLoading = false) })
    );
  }
}

export class RecipeService {
    API_URL =
      'XXX';
    private recipes: Recipe[] = [];
  
    recipesUpdate: Subject<void> = new Subject<void>();
  
    recipes$ = new BehaviorSubject<Recipe[]>(this.recipes);
  
    constructor(private http: HttpClient) { }
  
    getRecipes() {
      return this.http.get<Recipe[]>(`${this.API_URL}/recipes.json`)
    }
  
    getRecipeById(id: string) {
      return this.http.get<Recipe>(`${this.API_URL}/recipes/${id}.json`)
    }
  
    addRecipe(recipe: Recipe) {
      return this.http
        .post(`${this.API_URL}/recipes.json`, recipe)
        .subscribe((response) => {
          this.recipesUpdate.next();
        });
    }
  
    updateRecipe(recipe: Recipe) {
      return this.http
        .put(`${this.API_URL}/recipes/${recipe.id}.json`, recipe)
        .subscribe((response) => {
          this.recipesUpdate.next();
        });
    }
  
    deleteRecipe(id: string) {
      return this.http.delete(`${this.API_URL}/recipes/${id}.json`);
    }
  }

我不确定这是否是最好的方法,尤其是我使用空主题更新RecipeListComponent中的食谱列表并在ngOnInit方法中订阅它的方式.

我读了很多关于NgRx的 comments ,以及它对于简单的应用程序(https://blog.angular-university.io/angular-2-redux-ngrx-rxjs/)来说通常被认为是过度杀伤力,但我不确定如何在不使用它的情况下做到这一点.

此外,我不喜欢在删除、创建或更新食谱之后必须"重新加载"食谱列表并显示加载程序的事实.为此,我使用了Reaction Query和Reaction.有没有办法用Angular 来达到同样的效果?

prize 问题: 关于NgRx,在我的转角过程中,我几乎到了关于NgRx的部分,但我不确定我是否应该跟随它.你认为它值得学习吗?

推荐答案

首先,我想赞扬您出色的第一个问题文档.我还从马克西米利安·施瓦茨穆勒开始.

使用services进行状态管理是一种非常好的方法.总体而言,你在那里所做的一切都很好.我建议研究命令式代码和声明性代码(Joshua Morony在这个主题上有不错的内容).例如,您可以使用async pipe在模板中直接显示食谱,从而避免订阅麻烦.

正如您所描述的加载器,这种行为可以通过使用乐观渲染来缓解.当用户执行CRUD操作时,您在远程状态的同时对本地状态进行更改,不显示任何加载器,在出现错误的情况下,您回滚本地状态并向用户显示错误.

关于RecipeListComponent中的"Recipe$"的详细信息:当可观察序列中发生错误时,将触发catchError操作符,并处理该错误并返回一个新的可观察对象.在本例中,catchError操作符返回一个空数组的可观测数组(of([])).因此,将不会到达分接操作员,并且不会发生其副作用,即将isLoading值设置为假.

关于 prize 问题:我4个月前就开始使用ngrx了,我认为它是一个很棒的工具.在这一点上,我认为,如果ngrx是过度杀伤力,那么Angular 本身就是过度杀伤力.一旦你有了足够的练习,服务并不难.NGRX迫使您创建更透明、更干净的数据流,这可以为您节省大量调试时间.来了但是...但我确实认为,从学习曲线的Angular 来看,ngrx对于那些从Angular 出发的人来说是过头了.我肯定会等到我对Angular 本身有了坚实的把握后,才会增加更多的体重.

希望我没错过什么.

Refactor suggestion

仔细查看您的代码后,我发现了一些多余的操作.我建议重构您的代码.下面是一个获取食谱的例子:

当我们调用GET CREATES时,首先我们推送LOADING TRUE,以通知正在监听的每个人正在加载食谱.然后我们自己进行API调用.你可能会注意到take(1)管,它的目的是在首次发行后自动退订.根据您正在使用的后端,它可能是不必要的,但仍然是防止内存泄漏的好做法.

此外,我们有finalize管道将执行时,可观察到的完成或错误.

当接收到值时,它通过recipes$被发送.

export class RecipeService {
    API_URL = 'XXX';
    isLoading$ = new BehaviorSubject<boolean>(false);
    recipes$ = new BehaviorSubject<Recipe[]>([]);
    errorMessage: string;

    constructor(private http: HttpClient) { }

    getRecipes() {
        this.isLoading$.next(true);
        this.http.get(`${this.API_URL}/recipes.json`)
            .pipe(
                take(1),
                finalize(() => this.isLoading$.next(false))),
                catchError((error) => {
                    console.error('error retrieving recipes : ', error);
                    this.errorMessage = `Error retrieving recipes : ${error.error.error}`;
                    return of([]);
                }
            ).subscribe((recipes)=>{
                this.recipes$.next(recipes)
            })
    }
}

在初始化List组件时,我们执行getRecipe()来进行后端调用.

export class RecipeListComponent implements OnInit {
    recipes$: Observable<Recipe[]> = this.recipeService.recipes$;
    errorMessage: string;
    constructor(private recipeService: RecipeService) { }

    ngOnInit() {
        this.recipeService.getRecipes();
    }
}

模板将为:

<app-recipe-item *ngFor="let recipe of recipes$ | async"></app-recipe-item>

Angular相关问答推荐

*ngFor循环中令人困惑的Angular 行为

iOS Mobile Safari - HTML5视频覆盖一切

Angular 客户端应用程序无法从停靠容器解析后端服务的地址

Angular 和PrimeNg文件Uploader:渲染元件时如何显示选定文件的列表?

未在ng bootstrap 模式中设置表单输入

Sass-Loader中出现分号错误:ANGLE应用程序的缩进语法中不允许使用分号

当元素在元素上使用迭代变量时,如何重构ngFor?

如果Rxjs间隔请求仍在进行,则不应重置,但如果用户更改输入,则应重置

错误TS2531:对象可能为空.论窗体数组控件

ngrx 效果等到从初始值以外的其他状态收到响应

如何在 Angular 中顺序执行回调

Angular14:支持旧版浏览器的官方方式是什么?

带有重定向的Angular 2 AuthGuard服务?

Angular2订阅对子组件中@Input的更改

无法从模块it was neither declared nor imported导出服务

Angular 2 - ngfor 没有包装在容器中

在 RxJS Observable 中 flatten数组的最佳方法

Angular CLI 输出 - 如何分析Bundle 文件

@HostBinding 与 Angular 中的变量类

Lint 错误:实现生命周期挂钩接口