From 81242f8f423f4a7983ee75aa79663692f841de7a Mon Sep 17 00:00:00 2001 From: Nicolas Molina Monroy Date: Thu, 19 Sep 2024 06:19:55 -0400 Subject: [PATCH] migrate --- package-lock.json | 31 ++++++- package.json | 3 +- .../clear-btn/clear-btn.component.html | 10 ++- .../clear-btn/clear-btn.component.ts | 14 +-- .../components/counter/counter.component.html | 23 ++--- .../components/counter/counter.component.ts | 8 +- .../components/footer/footer.component.html | 41 ++++----- src/app/components/footer/footer.component.ts | 12 +-- src/app/components/header/header.component.ts | 6 +- src/app/components/todo/todo.component.ts | 10 +-- src/app/components/todos/todos.component.html | 20 ++--- src/app/components/todos/todos.component.ts | 13 ++- src/app/pages/home/home.component.html | 6 +- src/app/services/todos.store.ts | 87 +++++++++++++++++++ 14 files changed, 190 insertions(+), 94 deletions(-) create mode 100644 src/app/services/todos.store.ts diff --git a/package-lock.json b/package-lock.json index 095190f..428af3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "laboratorio-mydayapp-angular", + "name": "todo-app-ngrx", "version": "0.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "laboratorio-mydayapp-angular", + "name": "todo-app-ngrx", "version": "0.0.0", "dependencies": { "@angular/animations": "18.2.5", @@ -16,6 +16,7 @@ "@angular/platform-browser": "18.2.5", "@angular/platform-browser-dynamic": "18.2.5", "@angular/router": "18.2.5", + "@ngrx/signals": "^18.0.2", "rxjs": "7.5.0", "tslib": "2.3.0", "zone.js": "0.14.10" @@ -4076,6 +4077,24 @@ "win32" ] }, + "node_modules/@ngrx/signals": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/signals/-/signals-18.0.2.tgz", + "integrity": "sha512-FXmcY2cmkbhZtg9k8Ntq69SyelGmmb6fWtdButH4T8GGFH0o3f1FZTR829j4ynphy8SzuDhD/pzrnpWcV481oQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + }, + "peerDependenciesMeta": { + "rxjs": { + "optional": true + } + } + }, "node_modules/@ngtools/webpack": { "version": "18.2.5", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.5.tgz", @@ -17986,6 +18005,14 @@ "dev": true, "optional": true }, + "@ngrx/signals": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/signals/-/signals-18.0.2.tgz", + "integrity": "sha512-FXmcY2cmkbhZtg9k8Ntq69SyelGmmb6fWtdButH4T8GGFH0o3f1FZTR829j4ynphy8SzuDhD/pzrnpWcV481oQ==", + "requires": { + "tslib": "^2.3.0" + } + }, "@ngtools/webpack": { "version": "18.2.5", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.5.tgz", diff --git a/package.json b/package.json index f4df9a7..cfd38b4 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/platform-browser": "18.2.5", "@angular/platform-browser-dynamic": "18.2.5", "@angular/router": "18.2.5", + "@ngrx/signals": "^18.0.2", "rxjs": "7.5.0", "tslib": "2.3.0", "zone.js": "0.14.10" @@ -49,4 +50,4 @@ "karma-jasmine-html-reporter": "1.7.0", "typescript": "5.4.5" } -} +} \ No newline at end of file diff --git a/src/app/components/clear-btn/clear-btn.component.html b/src/app/components/clear-btn/clear-btn.component.html index fab5483..1efa51e 100644 --- a/src/app/components/clear-btn/clear-btn.component.html +++ b/src/app/components/clear-btn/clear-btn.component.html @@ -1,11 +1,13 @@ -@if (completedTodos$ | async; as todos) { - @if (todos.length > 0) { +@let completedTodos = store.completedTodos(); + + + @if (completedTodos.length > 0) { } -} + diff --git a/src/app/components/clear-btn/clear-btn.component.ts b/src/app/components/clear-btn/clear-btn.component.ts index e5950f1..f1f3e27 100644 --- a/src/app/components/clear-btn/clear-btn.component.ts +++ b/src/app/components/clear-btn/clear-btn.component.ts @@ -1,23 +1,13 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { AsyncPipe } from '@angular/common'; -import { TodoService } from '@services/todo.service'; +import { TodosStore } from '@services/todos.store'; @Component({ standalone: true, - imports: [AsyncPipe], selector: 'app-clear-btn', templateUrl: './clear-btn.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class ClearBtnComponent { - private todoService = inject(TodoService); - - - completedTodos$ = this.todoService.getCompletedTodos(); - - clear() { - this.todoService.clearCompleted(); - } - + readonly store = inject(TodosStore); } diff --git a/src/app/components/counter/counter.component.html b/src/app/components/counter/counter.component.html index b220132..8d76cb0 100644 --- a/src/app/components/counter/counter.component.html +++ b/src/app/components/counter/counter.component.html @@ -1,11 +1,12 @@ -@if (pendingTodos$ | async; as pendingTodos) { - - {{ pendingTodos.length }} - @if (pendingTodos.length === 1) { - item - } @else { - items - } - left - -} +@let pendingTodos = store.pendingTodos(); + + + {{ pendingTodos.length }} + @if (pendingTodos.length === 1) { + item + } @else { + items + } + left + + diff --git a/src/app/components/counter/counter.component.ts b/src/app/components/counter/counter.component.ts index 0f28e0b..8a14d42 100644 --- a/src/app/components/counter/counter.component.ts +++ b/src/app/components/counter/counter.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { AsyncPipe } from '@angular/common'; -import { TodoService } from '@services/todo.service'; +import { TodosStore } from '@services/todos.store'; @Component({ standalone: true, @@ -11,9 +11,5 @@ import { TodoService } from '@services/todo.service'; changeDetection: ChangeDetectionStrategy.OnPush }) export class CounterComponent{ - private todoService = inject(TodoService); - - - pendingTodos$ = this.todoService.getPendingTodos(); - + readonly store = inject(TodosStore); } diff --git a/src/app/components/footer/footer.component.html b/src/app/components/footer/footer.component.html index ffd9a24..98202bb 100644 --- a/src/app/components/footer/footer.component.html +++ b/src/app/components/footer/footer.component.html @@ -1,25 +1,26 @@ -@if (todos$ | async; as todos) { - @if (todos.length > 0) { - } diff --git a/src/app/components/footer/footer.component.ts b/src/app/components/footer/footer.component.ts index 07997de..8ed57a1 100644 --- a/src/app/components/footer/footer.component.ts +++ b/src/app/components/footer/footer.component.ts @@ -1,23 +1,17 @@ import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; import { RouterLink } from '@angular/router'; -import { AsyncPipe } from '@angular/common'; -import { TodoService } from '@services/todo.service'; +import { TodosStore } from '@services/todos.store'; import { CounterComponent } from '@components/counter/counter.component'; import { ClearBtnComponent } from '@components/clear-btn/clear-btn.component'; @Component({ standalone: true, - imports: [AsyncPipe, CounterComponent, ClearBtnComponent, RouterLink], + imports: [CounterComponent, ClearBtnComponent, RouterLink], selector: 'app-footer', templateUrl: './footer.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class FooterComponent { - private todoService = inject(TodoService); - - - todos$ = this.todoService.getTodos(); - filter$ = this.todoService.getFilter(); - + readonly store = inject(TodosStore); } diff --git a/src/app/components/header/header.component.ts b/src/app/components/header/header.component.ts index dd3381b..fd4e648 100644 --- a/src/app/components/header/header.component.ts +++ b/src/app/components/header/header.component.ts @@ -1,7 +1,7 @@ import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { TodoService } from '@services/todo.service'; +import { TodosStore } from '@services/todos.store'; @Component({ standalone: true, @@ -11,14 +11,14 @@ import { TodoService } from '@services/todo.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class HeaderComponent { - private todoService = inject(TodoService); + readonly store = inject(TodosStore); input = new FormControl('', { nonNullable: true }); addTodo() { const title = this.input.value.trim(); if (title !== '') { - this.todoService.add(title); + this.store.add(title); this.input.setValue(''); } } diff --git a/src/app/components/todo/todo.component.ts b/src/app/components/todo/todo.component.ts index 28a3b30..b965ff3 100644 --- a/src/app/components/todo/todo.component.ts +++ b/src/app/components/todo/todo.component.ts @@ -4,7 +4,7 @@ import { NgClass } from '@angular/common'; import { Todo } from '@models/todo.model'; -import { TodoService } from '@services/todo.service'; +import { TodosStore } from '@services/todos.store'; @Component({ standalone: true, @@ -14,7 +14,7 @@ import { TodoService } from '@services/todo.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class TodoComponent { - private todoService = inject(TodoService); + readonly store = inject(TodosStore); private cdRef = inject(ChangeDetectorRef); _todo!: Todo; @@ -28,18 +28,18 @@ export class TodoComponent { @ViewChild('inputElement') inputElement!: ElementRef; toggle() { - this.todoService.toggle(this._todo.id); + this.store.toggle(this._todo.id); } update() { const title = this.input.value.trim(); if (title !== '') { - this.todoService.update(this._todo.id, { title }); + this.store.update(this._todo.id, { title }); } } remove() { - this.todoService.remove(this._todo.id); + this.store.remove(this._todo.id); } escape() { diff --git a/src/app/components/todos/todos.component.html b/src/app/components/todos/todos.component.html index b9e7e46..62a193f 100644 --- a/src/app/components/todos/todos.component.html +++ b/src/app/components/todos/todos.component.html @@ -1,11 +1,11 @@ -@if (todos$ | async; as todos) { - @if (todos.length > 0) { -
-
    - @for (todo of todos; track todo) { - - } -
-
- } +@let todos = store.visibleTodos(); +@if (todos.length > 0) { +
+
    + @for (todo of todos; track todo.id) { + + } +
+
} + diff --git a/src/app/components/todos/todos.component.ts b/src/app/components/todos/todos.component.ts index 34adf94..645abaa 100644 --- a/src/app/components/todos/todos.component.ts +++ b/src/app/components/todos/todos.component.ts @@ -1,32 +1,29 @@ import { Component, OnInit, ChangeDetectionStrategy, inject } from '@angular/core'; -import { AsyncPipe } from '@angular/common'; import { ActivatedRoute } from '@angular/router'; import { Filter } from '@models/filter.model'; import { TodoComponent } from '@components/todo/todo.component'; -import { TodoService } from '@services/todo.service'; +import { TodosStore } from '@services/todos.store'; @Component({ standalone: true, - imports: [TodoComponent, AsyncPipe], + imports: [TodoComponent], selector: 'app-todos', templateUrl: './todos.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class TodosComponent implements OnInit { - private todoService = inject(TodoService); + readonly store = inject(TodosStore); private route = inject(ActivatedRoute); - todos$ = this.todoService.getTodosByFilter(); - constructor() { this.route.paramMap.subscribe((params) => { const filter = params.get('filter') as Filter; - this.todoService.changeFilter(filter || 'all'); + this.store.changeFilter(filter || 'all'); }); } ngOnInit(): void { - this.todoService.readStorage(); + // this.store.readStorage(); } } diff --git a/src/app/pages/home/home.component.html b/src/app/pages/home/home.component.html index ccb3962..abcfd12 100644 --- a/src/app/pages/home/home.component.html +++ b/src/app/pages/home/home.component.html @@ -1,7 +1,7 @@
- +
- - + +
diff --git a/src/app/services/todos.store.ts b/src/app/services/todos.store.ts new file mode 100644 index 0000000..dd45926 --- /dev/null +++ b/src/app/services/todos.store.ts @@ -0,0 +1,87 @@ +import { signalStore, withState, withMethods, patchState, withComputed } from '@ngrx/signals'; + +import { Todo, UpdateTodoDto } from '@models/todo.model'; +import { Filter } from '@models/filter.model'; +import { computed } from '@angular/core'; + +type TodosState = { + todos: Todo[]; + filter: Filter; +}; + +const initialState: TodosState = { + todos: [], + filter: 'all', +}; + +export const TodosStore = signalStore( + { providedIn: 'root' }, + withState(initialState), + withComputed((state) => ({ + visibleTodos: computed(() => { + const todos = state.todos(); + const filter = state.filter(); + + if (filter === 'pending') { + return todos.filter((todo) => !todo.completed); + } + if (filter === 'completed') { + return todos.filter((todo) => todo.completed); + } + return todos; + }), + pendingTodos: computed(() => { + return state.todos().filter((todo) => !todo.completed); + }), + completedTodos: computed(() => { + return state.todos().filter((todo) => todo.completed); + }), + })), + withMethods((store) => ({ + add(title: string): void { + const newTodo = { + id: 'id_' + Date.now(), + title, + completed: false, + }; + const todos = store.todos(); + patchState(store, { + todos: [...todos, newTodo], + }); + }, + remove(id: string): void { + const todos = store.todos(); + patchState(store, { + todos: todos.filter((todo) => todo.id !== id), + }); + }, + toggle(id: string): void { + patchState(store, (state) => ({ + todos: state.todos.map((todo) => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + ), + })) ; + }, + update(id: string, dto: UpdateTodoDto): void { + patchState(store, (state) => ({ + todos: state.todos.map((todo) => { + if (todo.id === id) { + return { + ...todo, + ...dto, + }; + } + return todo; + }), + })) ; + }, + changeFilter(change: Filter) { + patchState(store, { filter: change }); + }, + clearCompleted(): void { + patchState(store, (state) => ({ + todos: state.todos.filter((todo) => !todo.completed), + })) ; + }, + })) +);