我有一个带有全局数据服务的示例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);
}
}
Component:
<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的属性'filter'。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>
请告诉我什么是最好的方法?
我相信您会收到此错误,因为todo流的初始值为null:
private _todos$: BehaviorSubject<Todo[]> = new BehaviorSubject(null);
您管道中的早期回报:
if (!items$ || items$ == null || items$ === undefined) { return null; }
不会忽略空值,因为可观察的items$
不是空值,但是它发出的第一个值是(kws
)。
我将进行更改,以便待办事项的初始值不是空,而是空数组。这也将简化模板(不需要ngIf,它将始终有一个值。)
关于设计,我不确定,希望其他人可以给您更好的答案,这里有一些想法:
app-todo
组件,真的不是两个不同的组件吗?待办事项的内部列表也许是共享的一部分。当然取决于您的目标。
删除管道并将过滤保留在服务中:不仅仅是使用todo
方法,还需要添加notCompleted
和completed
方法(或其他方法)来返回已过滤的待办事项。
我喜欢考虑“智能”和“愚蠢”组件(只需在Google上搜索一堆文章即可)。在这种情况下,我认为这基本上意味着app-todo
组件将不接收管道,而是显示或不显示已完成的待办事项,待办事项服务等仅会接收待办事项数组作为其输入(已通过父组件中的async
管道)。如果添加了新的待办事项,则它可能是组件上的输出。