I have a todo application that is using the Subject as a Service approach to state management.
My todo service looks like this
import { Injectable } from '@angular/core';
import { Todo } from './todo.model';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject, map, pipe } from 'rxjs';
@Injectable()
export class TodosService {
private todos$ = new BehaviorSubject<Todo[]>([]);
constructor(private http: HttpClient) {
this.http
.get<Todo[]>('https://jsonplaceholder.typicode.com/users/1/todos')
.subscribe((todos) => {
this.todos$.next(todos);
});
}
public getTodos(): Observable<Todo[]> {
return this.todos$;
}
public getCompletedTodos(): Observable<Todo[]> {
return this.todos$.pipe(
map((todos) => todos.filter((todo) => todo.completed))
);
}
public addTodo(event: string) {
const newArray: Todo[] = [
...this.todos$.value,
{
userId: 1,
id: this.todos$.value.length,
title: event,
completed: false,
},
];
this.todos$.next(newArray);
}
}
And my add-todo component looks likes this.
import 'zone.js/dist/zone';
import { Component } from '@angular/core';
import { TodosService } from './todo.service';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-add-todo',
standalone: true,
imports: [CommonModule, FormsModule],
providers: [TodosService],
template: `
<label>Add Todo</label>
<input type="text" [(ngModel)]="todo" />
<button (click)="addNewTodo()">Submit</button>
`,
})
export class AddTodo {
todo: string = '';
constructor(private todoService: TodosService) {}
addNewTodo() {
this.todoService.addTodo(this.todo);
this.todo = '';
}
ngOnInit() {}
}
I know that my addTodo method is being called by the component because I can console log the newArray value and see it when I try to add a new one. But for some reason my new todo isn't being displayed.
What's weirder is that this works fine when I call addTodo inside the constructor of my service. When done this way, my component that displays the todos updates properly.
constructor(private http: HttpClient) {
this.http
.get<Todo[]>('https://jsonplaceholder.typicode.com/users/1/todos')
.subscribe((todos) => {
this.todos$.next(todos);
});
setTimeout(()=> {
this.addTodo('Todo from inside setTimeout')
}, 3000)
}
So why does my list-todo component not update when addTodo is called from a component, rather than inside the service itself?
I've created a stackblitz demo
As mentioned by @BizzyBob in the comments, if you provide TodosService
on component-level, each component will get their own instance of a TodosService
and there will be no shared states between them.
In your case you can solve this by providing TodosService
inside your App
component in your main.ts
and removing the individual provider arrays from your standalone components.
Your App
component will look like:
@Component({
selector: 'my-app',
standalone: true,
imports: [
CommonModule,
TodoList,
TodoListSubscribe,
HttpClientModule,
AddTodo,
],
providers: [TodosService],
template: `
<app-todo-list></app-todo-list>
<app-add-todo></app-add-todo>
`,
})
export class App {}