0

I am having simple set of data loaded into observable as below:

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);
  });
}

My UI looks like

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

completeTasks() will be called on a button click and executes successfully But the UI is never updated after completing the task operation. Using ChangeDetectionRef was my last option to try. I tried to run the operation using ngZone but couldn't succeed.

When I write this.tasks.subscribe() and console.log the data, I see that task has been removed from Observable but yea not UI update. What else I can try. Could someone please point me right direction.

Update

Here's the method within dataservice:

loadTasks(ep: string): Observable<any> {
    return this.http.get(ep + '/usertask');
}
4
  • hello, could you show us code of this.dataService.loadTasks(someurl) ? Commented Apr 17, 2018 at 7:24
  • are you using ChangeDetection.OnPush? Commented Apr 17, 2018 at 7:26
  • @TomaszKula OnPush detection are award from observable.next. i suspect loadTasks to simple API call from HttpClient. If yes, after first next, is never updated when tasks change. Commented Apr 17, 2018 at 7:28
  • @Yanis-git Yes its simple HttpClient request. Updated my post. Commented Apr 17, 2018 at 7:59

1 Answer 1

3

you need Observable self managed and dedicated for task list lifecycle.

Attached example with BehaviorSuject and TaskService. For this example, instead to load from ajax request, i just populate dummy data after 1 secs.

Then you have update / delete action who both have to update list according to action done by user.

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);
    }
}

Here we just manage view and ask service according to action (or lifecycle hook). On init we want to load from server, then if checkbox is changing, we want to update our reference, when we click on delete button, we want to remove from list (and may on server as well).

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>

now main code is on 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

Sign up to request clarification or add additional context in comments.

2 Comments

Awesome Yanis.. Thanks for such a beautiful explanation.. :)
My please @GuruprasadRao, happy coding :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.