Reputation: 85
I am new to angular and creating a CRUD todos app, in which todos get stored to localStorage. Adding, updating, deleting, getting all working but if i delete all completed todos it works only on refresh that means on clicking it deletes from localStorage but it wont get deleted from the screen
Here is my project files
app.component.html
<div class="container">
<div class="todo-wrapper">
<app-todo-input></app-todo-input>
<div *ngFor="let todo of allTodos">
<app-todo-item [todo]="todo"></app-todo-item>
</div>
<app-todo-footer [style.display]="allTodos.length <= 0 ? 'none': 'inline'"></app-todo-footer>
</div>
</div>
app.component.ts
import { Component } from '@angular/core';
import { TodoService } from './todo.service';
import { StorageService } from './storage.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public allTodos = [];
constructor(private todoService: TodoService, private storageService: StorageService){
};
ngOnInit(){
this.todoService.getTodos().subscribe(todos => this.allTodos = todos);
// this.storageService.getTodos().subscribe(todos => this.allTodos = todos);
}
}
todo.ts
export class Todo {
id: number;
text: string;
completed: boolean;
constructor(id: number, text: string, completed: boolean){
this.id = id;
this.text = text;
this.completed = completed;
}
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {MatButtonModule, MatCheckboxModule} from '@angular/material';
import { AppComponent } from './app.component';
import { TodoInputComponent } from './todo-input/todo-input.component';
import { TodoService } from './todo.service';
import { StorageService } from './storage.service';
import { TodoItemComponent } from './todo-item/todo-item.component';
import { TodoFooterComponent } from './todo-footer/todo-footer.component';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [
AppComponent,
TodoInputComponent,
TodoItemComponent,
TodoFooterComponent
],
imports: [
BrowserModule,
FormsModule,
BrowserAnimationsModule,
MatButtonModule,
MatCheckboxModule,
AppRoutingModule
],
providers: [
TodoService,
StorageService
],
bootstrap: [AppComponent]
})
export class AppModule { }
storage.service.ts
import { Injectable } from '@angular/core';
import { Todo } from './todo';
import { Observable, of, BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class StorageService {
constructor() { }
public setTodos(todos: Todo[]): void {
localStorage.setItem('todos', JSON.stringify({ todos: todos }))
}
public getTodos(): Observable<Todo[]>{
let localStorageItem = JSON.parse(localStorage.getItem('todos'));
if(localStorageItem == null){
return of([]);
}else{
return of(localStorageItem.todos);
}
}
}
todo.service.ts
import { Injectable, Input } from '@angular/core';
import { StorageService } from './storage.service';
import { Observable, of, BehaviorSubject } from 'rxjs';
import { Todo } from './todo';
@Injectable({
providedIn: 'root'
})
export class TodoService {
@Input()
private todo: Todo;
public allTodos: Todo[] = [];
private nextId: number;
constructor(private storageService: StorageService) {
this.loadTodos();
}
public addTodo(text: string) : void{
let todos = this.allTodos;
if (todos.length == 0) {
this.nextId = 0;
} else {
let maxId = todos[todos.length - 1].id;
this.nextId = maxId + 1;
}
let todo = new Todo(this.nextId, text, false);
todos.push(todo);
this.storageService.setTodos(todos);
this.nextId++;
// this.lengthTodos();
}
// public getTodos() {
// return this.allTodos;
// }
public loadTodos (){
return this.storageService.getTodos().subscribe(todos => this.allTodos = todos);
}
public getTodos(): Observable<Todo[]>{
return of(this.allTodos)
}
public removeTodo(selectedTodo): void{
let todos = this.allTodos;
todos.splice(todos.findIndex((todo) => todo.id == selectedTodo), 1);
this.storageService.setTodos(todos);
}
public deleteCompleted(){
let todos = this.allTodos;
let completedTodos = todos.filter(todo => todo.completed !== true);
todos = completedTodos;
this.storageService.setTodos(todos);
}
public update(id, newValue){
let todos = this.allTodos;
let todoToUpdate = todos.find((todo) => todo.id == id);
todoToUpdate.text = newValue;
this.storageService.setTodos(todos);
}
public isCompleted(id: number, completed: boolean){
let todos = this.allTodos;
let todoToComplete = todos.find((todo) => todo.id == id);
todoToComplete.completed = !todoToComplete.completed;
this.storageService.setTodos(todos);
}
// public lengthTodos(){
// let todos = this.storageService.getTodos();
// let activeTodos = todos.filter((todo) => !todo.completed).length;
// return activeTodos;
// }
}
todo-footer.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { TodoService } from '../todo.service';
import { Todo } from '../todo';
@Component({
selector: 'app-todo-footer',
templateUrl: './todo-footer.component.html',
styleUrls: ['./todo-footer.component.css']
})
export cl
ass TodoFooterComponent implements OnInit {
public allTodos;
private activeTasks : number = 0;
constructor(private todoService: TodoService) {
this.todoService.getTodos().subscribe(todos => this.allTodos = todos);
}
public getLength(){
// this.activeTasks = this.todoService.lengthTodos();
}
private clearCompleted(){
this.todoService.deleteCompleted();
}
ngOnInit() {
// this.getLength();
}
}
todo-footer.component.html
<footer class="footer">
<button class="clear-completed" (click)="clearCompleted()">Clear completed</button>
</footer>
In todo.service deleteCompleted method is not working that used by todo-footer.component in deleting all todos that are completed==true.
Upvotes: 0
Views: 167
Reputation: 18271
You've got an Observable here, but you're not actually using it. Instead, you're holding a single instance of the array in multiple components, and relying on that instance being modified, however filter
doesn't modify the instance in memory: it returns a new instance.
Create a BehaviourSubject
, like so:
private _todos = new BehaviorSubject<Todo[]>(null);
We can use this to update the Observable value as needed.
Change the getTodos
function like so:
public getTodos(): Observable<Todo[]>{
return this._todos.asObservable();
}
Now, any time you change the value of the todos
, you should call next
on the BehaviourSubject
. For example:
public deleteCompleted(){
let todos = this.allTodos;
let completedTodos = todos.filter(todo => todo.completed === false);
this.allTodos = completedTodos;
this.storageService.setTodos(this.allTodos);
// Emit the new value from the Observable
this._todos.next(this.allTodos);
}
Here is a fork of your Stackblitz to show it working
Upvotes: 2