【问题标题】:Observable with async pipe doesn't update UI Angular 4带有异步管道的 Observable 不会更新 UI Angular 4
【发布时间】:2018-04-17 07:19:05
【问题描述】:

我将一组简单的数据加载到observable 中,如下所示:

public tasks: Observable<UserTask[]>;
constructor(private dataService: HttpdataService, private changeDetector: ChangeDetectorRef) { }

ngOnInit() {
  this.loadTasks();
}

loadTasks() {
  this.tasks = this.dataService.loadTasks(someurl);
}

completeTasks(task: UserTask, url: string) {
  this.dataService.finishTasks(url, task.id).then(() => {
     this.toastr.success('Task completed successfully!', 'Success');
         this.tasks.subscribe(() => {
         this.changeDetector.markForCheck();
     });
  }).catch(err => {
    this.toastr.error('The request has errored out!', 'Error');
    console.error(err);
  });
}

我的用户界面看起来像

<tr *ngFor="let task of tasks | async">
   //td data
</tr>

completeTasks() 将在单击按钮时调用并成功执行,但在完成任务操作后 UI 永远不会更新。使用ChangeDetectionRef 是我尝试的最后一个选择。我尝试使用ngZone 运行该操作,但未能成功。

当我编写 this.tasks.subscribe()console.log 数据时,我看到该任务已从 Observable 中删除,但不是 UI 更新。我还能尝试什么。有人可以指点我正确的方向。

更新

这是dataservice中的方法:

loadTasks(ep: string): Observable<any> {
    return this.http.get(ep + '/usertask');
}

【问题讨论】:

  • 你好,你能告诉我们this.dataService.loadTasks(someurl)的代码吗?
  • 你在使用 ChangeDetection.OnPush 吗?
  • @TomaszKula OnPush 检测来自 observable.next。我怀疑 loadTasks 来自 HttpClient 的简单 API 调用。如果是,则在 first next 之后,在任务更改时永远不会更新。
  • @Yanis-git 是的,它很简单 HttpClient 请求。更新了我的帖子。

标签: angular asynchronous observable


【解决方案1】:

您需要 Observable 自我管理并专用于 task list lifecycle

附有BehaviorSujectTaskService 的示例。对于这个例子,我只是在 1 秒后填充虚拟数据,而不是从 ajax 请求加载。

然后你有更新/删除操作,他们都必须根据用户所做的操作来更新列表。

Component.ts:

import {
    Component,  OnInit
} from '@angular/core';

import {TaskModel, TaskService} from './services/task.service';


@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    constructor(public taskService: TaskService) { }

    ngOnInit() {
        this.taskService.fetchTask();
    }

    onChangeHandler(task: TaskModel)
    {
        this.taskService.save(task);
    }

    onDeleteHandler(task: TaskModel) {
        this.taskService.remove(task);
    }
}

这里我们只是根据操作(或生命周期钩子)管理视图和请求服务。在初始化时我们想从服务器加载,然后如果复选框发生变化,我们想更新我们的引用,当我们点击删除按钮时,我们想从列表中删除(也可能在服务器上)。

component.html

<h2>TODO LIST</h2>
<div *ngFor="let task of (taskService.task$ | async)">
    <p>
        {{ task.title }} | <input type="checkbox" [(ngModel)]="task.status" (change)="onChangeHandler(task)"> | <button (click)="onDeleteHandler(task)"> Delete </button>
    </p>
</div>

现在主要代码在 task.service 上:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import {isUndefined} from 'util';

export interface TaskModel {
    id: number;
    title: string;
    status: boolean;
}

@Injectable()
export class TaskService {
    /**
     * Observable who should always be the replica of last tasks state.
     */
    private _tasks$: BehaviorSubject<TaskModel[]>;
    /**
     * array of task, use for each action done on task.
     */
    private tasks: TaskModel[];

    constructor() {
        // We init by empty array.
        this._tasks$ = new BehaviorSubject([]);
        this.tasks = [];
    }

    /**
     * Fake fetch data from server.
     */
    fetchTask()
    {
        // Fake request.
        Observable.create(obs => {
            /**
             * After 1 secs, we update internal array and observable.
             */
            setTimeout(() => {
                this.tasks = this.getDummyData();
                obs.next(this.tasks);
            }, 1000);

        }).subscribe(state => this._tasks$.next(state));
    }

    /**
     * Magic getter
     * @return {Observable<{id: number; title: string; status: boolean}[]>}
     */
    get task$(): Observable<TaskModel[]> {
        // return only observable, don't put public your BehaviorSubject
        return this._tasks$.asObservable();
    }

    /**
     * We update from internal array reference, and we next fresh data with our observable.
     * @param {TaskModel} task
     */
    save(task: TaskModel) {
        const index = this.tasks.findIndex(item => item.id === task.id);
        if(!isUndefined(this.tasks[index]))
        {
            this.tasks[index] = task;
        }

        // Notify rest of application.
        this._tasks$.next(this.tasks);
    }

    /**
     * We remove from internal array reference, and we next data with our observable.
     * @param {TaskModel} task
     */
    remove(task: TaskModel) {
        this.tasks = this.tasks.filter(item => item.id !== task.id);
        this._tasks$.next(this.tasks);
    }

    /**
     * Fake data.
     * @return {{id: number; title: string; status: boolean}[]}
     */
    private getDummyData() : TaskModel[]
    {
        return [
            {
                id: 1,
                title: 'task1',
                status: true
            },
            {
                id: 2,
                title: 'task2',
                status: false
            },
            {
                id: 3,
                title: 'task3',
                status: true
            }
        ];
    }
}

Online code

【讨论】:

  • 真棒Yanis..谢谢你这么漂亮的解释.. :)
  • 请@GuruprasadRao,编码愉快:)
猜你喜欢
  • 1970-01-01
  • 2016-08-20
  • 2016-11-04
  • 2019-07-17
  • 1970-01-01
  • 2017-11-20
  • 1970-01-01
  • 2023-03-22
  • 2020-11-13
相关资源
最近更新 更多