I am trying to make a simple TODO app in angular using RXJS. I have a JSON server mock database with my TODO tasks.
So I ended up with this TasksService:
@Injectable({
providedIn: 'root'
})
export class TasksService
{
private _tasks : ITask[] = [];
private _tasks$: BehaviorSubject<ITask[]> = new BehaviorSubject<ITask[]>([]);
constructor (private _http: HttpClient) { }
public getTasks()
{
this.getTasksObservableFromDb().pipe(
tap(
(tasks) =>
{
this._tasks = tasks;
this._tasks$.next(tasks);
}
)
).subscribe();
return this._tasks$;
}
public addTask(task: ITask)
{
this._tasks.push(task);
this._tasks$.next(this._tasks);
}
private getTasksObservableFromDb(): Observable<any>
{
return this._http.get<any>('http://127.0.0.1:3000/tasks');
}
When I add task I don't want to post them to the server right away. So when I get my tasks from the server, I save them to the _tasks property and then pass them to the next() method for my _tasks$: BehaviorSubject. Because later I want to post my tasks to the server in bulk and now I just want them to display properly in Angular.
In my AppComponent I get my tasks and assign them to my tasks property.
export class AppComponent implements OnInit
{
public tasks!:BehaviorSubject<ITask[]>;
constructor (private _tasksService: TasksService)
{}
ngOnInit(): void
{
console.log('OnInit');
this.tasks = this._tasksService.getTasks();
}
public addTask()
{
this._tasksService.addTask(
{
id: crypto.randomUUID(),
isImportant: true,
text: 'Added task'
}
);
}
}
In my HTML template I use an async pipe for my tasks property and display my tasks:
<ng-container *ngFor="let task of tasks | async">
{{task.text}}
{{task.id}}
</ng-container>
<button type="button" (click)="addTask()">Add Task</button>
But later I accidentally deleted this line in my TaskService:
this._tasks$.next(this._tasks);
So my method now looks like this:
public addTask(task: ITask)
{
this._tasks.push(task);
}
But adding tasks still works! Angular displays newly added tasks even though I don't pass new task array for my BehaviorSubject.
So I decided to log values from my tasks! : BehaviorSubject<ITask[]> property in my AppComponent class:
public addTask()
{
this._tasksService.addTask(
{
id: crypto.randomUUID(),
isImportant: true,
text: 'Added task'
}
);
this.tasks.pipe(tap((value) => console.log(value)
)).subscribe();
}
And task is added as expected - every time a get an array with one more task:
Array(3) [ {…}, {…}, {…} ] <- Add task button is clicked
Array(4) [ {…}, {…}, {…}, {…} ] <- Add task button is clicked
Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- Add task button is clicked
But when I return this line to my addTask method in TaskService:
this._tasks$.next(this._tasks);
I get these logs:
Array(3) [ {…}, {…}, {…} ] <- Add task button is clicked -> one task is added
Array(4) [ {…}, {…}, {…}, {…} ] <- Add task button is clicked -> one task is added
Array(4) [ {…}, {…}, {…}, {…} ] <- I get the same array
Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- Add task button is clicked -> one task is added
Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- I get the same array
Array(5) [ {…}, {…}, {…}, {…}, {…} ] <- I get the same array once again
So I am kinda lost why the observable behaves like this... Maybe I don't fully understand the next() method?
You get the same values in console because you add a new subscription each time when you click button. Move the subscription to a constructor or OnInit.
When you fix it you will ask why you get the new message to screen but there is not in console. This is because when you this._tasks$.next(tasks) in getTasksObservableFromDb you send memory link to the same tasks array and this is enough to display the messages on the screen when the array is changed. but it's not enough to the behaviorsubject know that the object was changed. To avoid it you should point a new array with help this._tasks$.next([...tasks]) (in getTasksObservableFromDb). After this you code will not work without the next.
Full example https://stackblitz.com/edit/angular-vsrfhx?file=src/tasks.service.ts