【问题标题】:Angular - Filtering Global Data Service for Multiple Component InstancesAngular - 过滤多个组件实例的全局数据服务
【发布时间】:2020-08-08 13:18:55
【问题描述】:

我有一个带有全局数据服务的示例 Angular 应用程序,我试图通过过滤它在组件的多个实例之间共享它。这是我更大应用程序的原型,但我在两者中都遇到了同样的问题。我尝试使用管道过滤器,但收到错误消息。现在,我想要一些关于在这种情况下的最佳实践的指导,因为这是我第一次尝试这样做。如果是管道过滤,那么我需要有关错误消息的帮助。

我的示例使用返回 ToDo 项的测试 API。我想将该服务用于在一个实例中显示已完成项目而在另一个实例中显示未完成项目的组件。也有 userId 过滤只是因为我觉得我需要另一个元素。

服务:

    import { Injectable } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    import { Observable, BehaviorSubject } from 'rxjs';

    export interface Todo {
      userId: number;
      id: number;
      title: string;
      completed: boolean;
    }

    @Injectable({
      providedIn: 'root'
    })
    export class TodosService {
      private _todos$: BehaviorSubject<Todo[]> = new BehaviorSubject(null);

      constructor(private http: HttpClient) { }

      // Public Methods
      get todos() {
        return this._todos$.asObservable();
      }

      loadData() {
        this._listTodos()
          .subscribe(
            res => {
              this._todos$.next(res);
            },
            err => console.log('Error receiving todo items.', err)
          );
      }
      addTodo(todo: Todo) {
        this._addTodo(todo).subscribe( item => {
          const d = this._todos$.getValue();
          d.push(item);
          this._todos$.next(d);
        });
      }

      // Private methods
      _listTodos(): Observable<Todo[]> {
        return this.http.get<Todo[]>('https://jsonplaceholder.typicode.com/todos');
      }
      _addTodo(todo: Todo): Observable<Todo> {
        return this.http.post<Todo>('https://jsonplaceholder.typicode.com/todos', todo);
      }
    }

组件:

    <div>
        <button (click)="addTodo()">Add Todo</button>
    </div>
    <ng-container *ngIf="(todosService.todos | todoFilter$: complete: userId | async) as todos">
        <h1>Completed: {{ complete }} UserId: {{ userId }} </h1>
        <ul>
            <li *ngFor="let todo of todos">
                {{ todo.id}} - {{ todo.userId }} - {{ todo.title }}
            </li>
        </ul>
    </ng-container>

    import { Component, OnInit, Input, OnChanges } from '@angular/core';
    import { Todo, TodosService} from '../../services/todos.service';
    @Component({
      selector: 'app-todo',
      templateUrl: './todo.component.html',
      styleUrls: ['./todo.component.css']
    })
    export class TodoComponent implements OnChanges {
      @Input() complete: false;
      @Input() userId: number = null;

      constructor(private todosService: TodosService) { }

      ngOnChanges() {
        this.load();
      }

      async load() {
        await this.todosService.loadData();
      }

      addTodo() {
        const t: Todo = {
          id: 999,
          userId: this.userId,
          title: 'Css Add',
          completed: this.complete
        };
        this.todosService.addTodo(t);
      }

    }

管道过滤器(抛出错误:无法读取 null 的属性“过滤器”。kws 显然为 null,但我不确定为什么填充 items$?):

    import { Pipe, PipeTransform } from '@angular/core';
    import { Todo } from '../services/todos.service';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';

    @Pipe({
      name: 'todoFilter$'
    })
    export class TodoFilterPipe implements PipeTransform {

      transform(items$: Observable<Todo[]>, complete: boolean, userId: number): any {
        if (!items$ || items$ == null || items$ === undefined) { return null; }

        return items$.pipe(map(
          kws => kws.filter(kw => (kw.completed === complete && (!userId || kw.userId === userId))
          ))
        );
      }
    }

应用组件示例页面:

<app-todo complete="false"></app-todo>
<app-todo complete="true" userId="1"></app-todo>

请告诉我最好的方法是什么?

【问题讨论】:

    标签: angular filter service observable


    【解决方案1】:

    我相信你得到了错误,因为 todo 流的初始值为 null:

     private _todos$: BehaviorSubject<Todo[]> = new BehaviorSubject(null);
    

    你的管道中的早期回报:

     if (!items$ || items$ == null || items$ === undefined) { return null; }
    

    不会忽略 null,因为可观察到的 items$ 不是 null,但它发出的第一个值是 (kws)。

    我会进行更改,以便 todos 的初始值不是 null,而是一个空数组。这也将简化模板(不需要 ngIf,它总是有一个值。)

    在设计方面我不确定,希望其他人能给你一个更好的答案,这里有一些想法:

    1. app-todo组件,真的不是两个不同的组件吗?也许 todos 的内部列表是共享部分。当然取决于你的目标。

    2. 移除管道并在服务中保留过滤:不只是使用todo 方法,而是添加返回过滤后的待办事项的notCompletedcompleted 方法(或其他方法)。

    3. 我喜欢在“智能”和“愚蠢”组件中思考(只需 google 上一堆关于它的文章)。在这种情况下,我认为这基本上意味着app-todo 组件可能会不知道管道,而是显示已完成的待办事项,待办事项服务等将仅接收待办事项数组作为其输入(已经通过父组件中的async 管道)。如果添加了新的待办事项,它可能是组件的输出。

    【讨论】:

    • 好的,我尝试将它初始化到一个空白数组,现在没有错误,但我没有得到任何数据。在搞砸了一些之后,我不得不将 filter() 中的所有比较转换为字符串?我不确定为什么会这样?这就是我最终得到的结果: kws => kws.filter(kw => (kw.completed.toString() === complete.toString() && (!userId || kw.userId.toString() === userId.toString()))
    • 啊,我想这是因为@Input() complete: false;,当像这样绑定时complete="false"实际上是一个字符串而不是布尔值。您可以将其转换为组件中的布尔值或将绑定更改为[complete]="false"(这意味着complete javascript 的值,而不是文本)如果您仍然有问题,我建议在@987654321 中重新创建您的原型@,这样其他人就更容易测试、编辑并提出更多建议。
    猜你喜欢
    • 1970-01-01
    • 2020-11-19
    • 1970-01-01
    • 2021-01-24
    • 2020-12-20
    • 1970-01-01
    • 2020-10-23
    • 2019-11-11
    • 1970-01-01
    相关资源
    最近更新 更多