I'm learning Angular from the very beginning, and ing from React, I can't find a way to manage a simple state in Angular. It's a simple todo app, and my strategy is to create a Todos array in todo.service.ts
@Injectable()
export class TodoService {
todos: Todo[] = [
{ id: 0, title: 'Cooking', pleted: false }
]
nextId: number = 1;
constructor() { }
public getTodos(): Todo[] {
return this.todos;
}
public addTodo(title: string): any {
this.newTodo = new Todo({ id: this.nextId, title, pleted: false })
this.todos.push(this.newTodo);
this.newTodo = new Todo();
this.nextId++
}
public deleteTodo(chosenTodo: Todo): any {
this.todos = this.todos.filter(todo => todo.id !== chosenTodo.id)
}
}
Now on the ListingComponent, I want to get all Todos, and then pass it down to ListingItemComponent, as such
todos: Todo[] = []
todoTitle: string = '';
constructor(private todoService: TodoService) { }
ngOnInit(): void {
this.todos = this.todoService.getTodos();
this.todoTitle = 'Do Something';
}
addTodo() {
this.todoService.addTodo(this.todoTitle);
}
<div *ngFor="let todo of todos">
<todo-listing-item [todo]="todo"></todo-listing-item>
</div>
/* This just adds a todo with title 'Do Something' for testing */
<button (click)="addTodo()">Add Todo</button>
Finally in ListingItemComponent
@Input() todo: Todo
constructor(private todoService: TodoService) { }
ngOnInit(): void {
}
deleteTodo(): void {
this.todoService.deleteTodo(this.todo)
}
<h1>{{todo.title}}</h1>
<button (click)="deleteTodo()">Delete Todo</button>
Now the ponent has no error, and I made sure to declare everything, but it's not working. Clicking the button won't change anything. After digging in, I changed the ListingComponent into "let todo of todoService.getTodos()"
, and remove the todos in the ts file along with the ngOnInit, and somehow it works. My guessing is that the ListingComponent doesn't know that the todos array has changed, and so it's not rerendering?
- So if I was to initialize the todos in the ngOnInit like I did before, do I need some other life-cycle to listen to the change of todos?
If many ponents need the todos array, should I write
ngOnInit(): void { this.todos = this.todoService.getTodos();}
on every single one of them? And if one ponent changes their todos, can other ponents listen and rerender as well?Thank you for reading. In React I would just create a global state, along with the functions, and pass it as props down to every child. But I can't wrap my head around managing state in Angular. Is it because I'm thinking in React, while I should not?
I'm learning Angular from the very beginning, and ing from React, I can't find a way to manage a simple state in Angular. It's a simple todo app, and my strategy is to create a Todos array in todo.service.ts
@Injectable()
export class TodoService {
todos: Todo[] = [
{ id: 0, title: 'Cooking', pleted: false }
]
nextId: number = 1;
constructor() { }
public getTodos(): Todo[] {
return this.todos;
}
public addTodo(title: string): any {
this.newTodo = new Todo({ id: this.nextId, title, pleted: false })
this.todos.push(this.newTodo);
this.newTodo = new Todo();
this.nextId++
}
public deleteTodo(chosenTodo: Todo): any {
this.todos = this.todos.filter(todo => todo.id !== chosenTodo.id)
}
}
Now on the ListingComponent, I want to get all Todos, and then pass it down to ListingItemComponent, as such
todos: Todo[] = []
todoTitle: string = '';
constructor(private todoService: TodoService) { }
ngOnInit(): void {
this.todos = this.todoService.getTodos();
this.todoTitle = 'Do Something';
}
addTodo() {
this.todoService.addTodo(this.todoTitle);
}
<div *ngFor="let todo of todos">
<todo-listing-item [todo]="todo"></todo-listing-item>
</div>
/* This just adds a todo with title 'Do Something' for testing */
<button (click)="addTodo()">Add Todo</button>
Finally in ListingItemComponent
@Input() todo: Todo
constructor(private todoService: TodoService) { }
ngOnInit(): void {
}
deleteTodo(): void {
this.todoService.deleteTodo(this.todo)
}
<h1>{{todo.title}}</h1>
<button (click)="deleteTodo()">Delete Todo</button>
Now the ponent has no error, and I made sure to declare everything, but it's not working. Clicking the button won't change anything. After digging in, I changed the ListingComponent into "let todo of todoService.getTodos()"
, and remove the todos in the ts file along with the ngOnInit, and somehow it works. My guessing is that the ListingComponent doesn't know that the todos array has changed, and so it's not rerendering?
- So if I was to initialize the todos in the ngOnInit like I did before, do I need some other life-cycle to listen to the change of todos?
If many ponents need the todos array, should I write
ngOnInit(): void { this.todos = this.todoService.getTodos();}
on every single one of them? And if one ponent changes their todos, can other ponents listen and rerender as well?Thank you for reading. In React I would just create a global state, along with the functions, and pass it as props down to every child. But I can't wrap my head around managing state in Angular. Is it because I'm thinking in React, while I should not?
- You got the part about shared services right. If you want multiple ponents to observe this change, you can use Observables to notify all the subscribes of this change. angular.io/guide/observables-in-angular – sinanspd Commented May 10, 2020 at 2:27
- As mentioned above, you need to use Observables. Here's a good tutorial dev.to/avatsaev/… – xandermonkey Commented May 10, 2020 at 2:28
- 1 If you're looking for more robust state management, I'd remend ngrx.io – xandermonkey Commented May 10, 2020 at 2:28
- @xandermonkey yes I've heard about ngrx, but this is my first ever app, so I want to stay away from libraries for now, but i'll definitely check it out. – Free Me Commented May 10, 2020 at 2:30
-
1
@FreeMe Correct. you shouldn't need any extra life cycle hooks. Although they are NOT the same thing, you can think of the behaviour similar to plain JS event listeners, it doesn't really matter what sub-scope you initialize them, they will get executed. You would still have a
todos
array in your ponent. Inside yoursubscribe
you would mutate that array and yourngFor
would pick up that change – sinanspd Commented May 10, 2020 at 2:37
4 Answers
Reset to default 3Let me first say that I'm a huge fan of NGRX and the pattern, and I suggest you make this part of your learning path. However, if you're looking for simple state management than a simple Observable will do the trick.
I created a stack overflow to illustrate the technique using the code you provided: https://stackblitz./edit/angular-ivy-kfw1sd. In this demonstration, I'm leveraging BehaviorSubject which is a special type of observable.
In closing, do your best to avoid .subscribe() and use the async pipe instead.
Hope this helps, Isaac
If you want to have a simple global state, you can start using Shared Services, obviously it will be only for learning purposes, because if you want to use in a real project, you can use Shared Services, but also, you would need Facade or other Design Patterns.
Main advantage using Facade is that you can migrate to NgRx easier.
I have an example using API + State + Base Architecture: https://github./cmandamiento/angular-architecture-base
Feel free to reach me out.
Indeed you do not always need to add another Library to manage state in Angular. Angular es with RxJS out of the box...
All the well known state management libraries use RxJS/BehaviorSubject internally. This article shows how to write a simple DIY state management Class which can be extended by Angular services.
Simple yet powerful state management in Angular with RxJS
This solution is also based on RxJS/BehaviorSubject.
Maybe it can be useful for you. The code example in the article is a Todo App :)
Disclaimer: I'm the author of the library
ng-simple-state
Simple state management in Angular with only Services and RxJS
- See the stackblitz demo.
- See the source code.