Angular - фильтрация глобальной службы данных для многокомпонентных экземпляров - PullRequest
0 голосов
/ 24 апреля 2020

У меня есть пример приложения Angular с глобальной службой данных, который я пытаюсь разделить между несколькими экземплярами компонента путем его фильтрации. Это прототип для моего более крупного приложения, но у меня одна и та же проблема в обоих случаях. Я пытался использовать трубу фильтра, но я получаю сообщение об ошибке. На данный момент я хотел бы получить некоторые рекомендации по наилучшей практике в этой ситуации, поскольку я впервые пытаюсь это сделать. Если это конвейерная фильтрация, то мне нужна помощь в сообщении об ошибке.

В моем примере используется тестовый API, который возвращает элементы ToDo. Я хочу использовать сервис для компонента, который показывает завершенные элементы в одном экземпляре и неполные элементы в другом. Существует также фильтрация по 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);
      }

    }

Pipe Filter (выдает ошибку: невозможно прочитать свойство 'filter' из null. Очевидно, kws является нулевым но я не уверен, почему, если элементы $ заполнены?):

    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>

Скажите, пожалуйста, как лучше всего это сделать?

1 Ответ

0 голосов
/ 25 апреля 2020

Я полагаю, вы получите ошибку, потому что начальное значение потока todo равно нулю:

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

Ранний возврат в вашем канале:

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

Не будет игнорировать ноль , поскольку наблюдаемая items$ не равна нулю, но первое значение, выдаваемое ею, является (kws).

Я бы изменила так, чтобы вместо нуля начальное значение задач было пустым массивом. , Это также упростит шаблон (нет необходимости в ngIf, он всегда будет иметь значение.)

С точки зрения дизайна я не уверен, надеюсь, кто-то другой может дать вам лучший ответ, вот Несколько мыслей:

  1. Компонент app-todo, неужели это не два разных компонента? Возможно, внутренний список задач является общей частью. Конечно, зависит от вашей цели.

  2. Удалите канал и сохраните фильтрацию в сервисе: вместо использования только метода todo добавьте методы notCompleted и completed ( или что-то в этом роде), которые возвращают отфильтрованные задачи.

  3. Мне нравится думать о «умных» и «немых» компонентах (просто посмотрите в Google кучу статей об этом). В этом контексте я думаю, что в основном это означает, что, возможно, компонент app-todo вместо того, чтобы знать о каналах, будет показывать завершенные задачи или нет, службу todo, et c. будет просто получать массив Todos в качестве входных данных (уже переданных через канал async в родительском компоненте). Если добавлен новый todo, это может быть вывод для компонента.

...