From 85959ec00afdefd6d2a28e2424598e2aee3540cd Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Mon, 24 Aug 2020 11:56:45 -0300 Subject: [PATCH 01/12] initial fix: custom validator declaration --- angular.json | 5 ++++- src/app/app.component.ts | 35 +++++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/angular.json b/angular.json index 1c7e6d6..2ecf625 100644 --- a/angular.json +++ b/angular.json @@ -129,5 +129,8 @@ } } }, - "defaultProject": "ng-peti" + "defaultProject": "ng-peti", + "cli": { + "analytics": false + } } \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b31f041..0f24e4a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,32 +1,43 @@ import { Component } from '@angular/core'; -import { FormGroup, FormControl, Form, Validators, AbstractControl } from '@angular/forms'; +import { + FormGroup, + FormControl, + Validators, + AbstractControl, + ValidatorFn, +} from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] + styleUrls: ['./app.component.scss'], }) export class AppComponent { title = 'Todo'; profileForm = new FormGroup({ - firstName: new FormControl('', [Validators.required, Validators.maxLength(10)]), + firstName: new FormControl('', [ + Validators.required, + Validators.maxLength(10), + ]), lastName: new FormControl('', [Validators.required, StartsWithAValidator]), - }) - constructor () { - this.profileForm.valueChanges.subscribe(value => console.log(value)); - } + }); + constructor() { + this.profileForm.valueChanges.subscribe((value) => console.log(value)); + } onSubmit() { console.log(this.profileForm.value); } initialize() { - this.profileForm.reset() - } + this.profileForm.reset(); + } } -export function StartsWithAValidator(control: AbstractControl) { +export const StartsWithAValidator = (): ValidatorFn => ( + control: AbstractControl +): { [key: string]: any } | null => { if (!control.value.startsWith('A')) { - return { startsWithA: true }; + return { startsWithA: false }; } return null; -} \ No newline at end of file +}; From 5d3729144633eb181ebf9bd9c88f1eb9357f6415 Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Mon, 24 Aug 2020 13:34:27 -0300 Subject: [PATCH 02/12] Use reactive-forms for the task form --- .gitignore | 1 + src/app/todo-form/todo-form.component.html | 16 ++++++--------- src/app/todo-form/todo-form.component.ts | 23 ++++++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 86d943a..301cefc 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ speed-measure-plugin*.json *.sublime-workspace # IDE - VSCode +.vscode/ .vscode/* !.vscode/settings.json !.vscode/tasks.json diff --git a/src/app/todo-form/todo-form.component.html b/src/app/todo-form/todo-form.component.html index ec0ad47..727095a 100644 --- a/src/app/todo-form/todo-form.component.html +++ b/src/app/todo-form/todo-form.component.html @@ -1,10 +1,6 @@ - - - - - - +
+ + +
diff --git a/src/app/todo-form/todo-form.component.ts b/src/app/todo-form/todo-form.component.ts index 3366020..3e2b117 100644 --- a/src/app/todo-form/todo-form.component.ts +++ b/src/app/todo-form/todo-form.component.ts @@ -1,24 +1,27 @@ -import { Component, OnInit, Output, EventEmitter} from '@angular/core'; +import { Component, OnInit, Output, EventEmitter } from '@angular/core'; import { TodoItem } from '../model/todo-item'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; @Component({ selector: 'app-todo-form', templateUrl: './todo-form.component.html', - styleUrls: ['./todo-form.component.scss'] + styleUrls: ['./todo-form.component.scss'], }) export class TodoFormComponent { - + taskForm = new FormGroup({ + description: new FormControl('', [Validators.required]) + }); @Output() add = new EventEmitter(); - save(description){ - if(!description.value || description.value === '') { - return; - } + get description() { + return this.taskForm.get('description'); + } + + onSubmit() { let task = new TodoItem(); - task.description = description.value; + task.description = this.description.value; task.isCompleted = false; this.add.emit(task); - description.value = ''; + this.taskForm.reset(); } } - From 56dee75c7ff7f8e710e1daf4bc723c5f5a6b3d1e Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Mon, 24 Aug 2020 19:01:28 -0300 Subject: [PATCH 03/12] Add a second field for task url and validate it against a custom validator --- src/app/custom-validators.ts | 5 +++++ src/app/model/todo-item.ts | 1 + src/app/todo-form/todo-form.component.html | 1 + src/app/todo-form/todo-form.component.scss | 10 +++++++++- src/app/todo-form/todo-form.component.ts | 8 +++++++- src/app/todo-list/todo-list.component.html | 22 ++++++++++++---------- 6 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 src/app/custom-validators.ts diff --git a/src/app/custom-validators.ts b/src/app/custom-validators.ts new file mode 100644 index 0000000..e1ba977 --- /dev/null +++ b/src/app/custom-validators.ts @@ -0,0 +1,5 @@ +import { ValidatorFn, AbstractControl, Validators } from '@angular/forms'; + +export const isUrl = Validators.pattern( + /^(http[s]?:\/\/){0,1}(www\.){0,1}[a-zA-Z0-9\.\-]+\.[a-zA-Z]{2,5}[\.]{0,1}/ +); diff --git a/src/app/model/todo-item.ts b/src/app/model/todo-item.ts index bdb4f8b..f4d4be4 100644 --- a/src/app/model/todo-item.ts +++ b/src/app/model/todo-item.ts @@ -1,6 +1,7 @@ export class TodoItem { id: number; description: string; + url: string; isCompleted: boolean = false; toggleCompleted() { diff --git a/src/app/todo-form/todo-form.component.html b/src/app/todo-form/todo-form.component.html index 727095a..b02ebd5 100644 --- a/src/app/todo-form/todo-form.component.html +++ b/src/app/todo-form/todo-form.component.html @@ -1,5 +1,6 @@
+ diff --git a/src/app/todo-form/todo-form.component.scss b/src/app/todo-form/todo-form.component.scss index 213bb36..ce87b8f 100644 --- a/src/app/todo-form/todo-form.component.scss +++ b/src/app/todo-form/todo-form.component.scss @@ -1,3 +1,11 @@ .add-todo { - width: 80% + width: 80%; +} + +input.ng-valid { + border: 3px solid green; +} + +input.ng-invalid { + border: 3px solid red; } diff --git a/src/app/todo-form/todo-form.component.ts b/src/app/todo-form/todo-form.component.ts index 3e2b117..a300660 100644 --- a/src/app/todo-form/todo-form.component.ts +++ b/src/app/todo-form/todo-form.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, Output, EventEmitter } from '@angular/core'; import { TodoItem } from '../model/todo-item'; import { FormGroup, FormControl, Validators } from '@angular/forms'; +import * as customValidators from '../custom-validators'; @Component({ selector: 'app-todo-form', @@ -9,17 +10,22 @@ import { FormGroup, FormControl, Validators } from '@angular/forms'; }) export class TodoFormComponent { taskForm = new FormGroup({ - description: new FormControl('', [Validators.required]) + description: new FormControl('', [Validators.required]), + url: new FormControl('', [Validators.required, customValidators.isUrl]), }); @Output() add = new EventEmitter(); get description() { return this.taskForm.get('description'); } + get url() { + return this.taskForm.get('url'); + } onSubmit() { let task = new TodoItem(); task.description = this.description.value; + task.url = this.url.value; task.isCompleted = false; this.add.emit(task); this.taskForm.reset(); diff --git a/src/app/todo-list/todo-list.component.html b/src/app/todo-list/todo-list.component.html index 127ec76..e7f55eb 100644 --- a/src/app/todo-list/todo-list.component.html +++ b/src/app/todo-list/todo-list.component.html @@ -1,11 +1,13 @@
    -
  • - {{task.description}} - - - - -
  • -
+
  • + {{ task.description }} {{ task.url }} + + + + +
  • + From c1dc0dda5b8d8cb883f8f6b137bc634b74999ede Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Mon, 24 Aug 2020 23:21:32 -0300 Subject: [PATCH 04/12] Add edit functionality --- src/app/todo-app/todo-app.component.html | 14 ++++---- src/app/todo-app/todo-app.component.ts | 20 +++++++----- src/app/todo-form/todo-form.component.html | 2 +- src/app/todo-form/todo-form.component.ts | 38 ++++++++++++++++++---- src/app/todo-list/todo-list.component.html | 3 +- src/app/todo-list/todo-list.component.ts | 26 +++++++-------- src/app/todo.service.ts | 24 +++++++------- 7 files changed, 80 insertions(+), 47 deletions(-) diff --git a/src/app/todo-app/todo-app.component.html b/src/app/todo-app/todo-app.component.html index 786cc57..37378f1 100644 --- a/src/app/todo-app/todo-app.component.html +++ b/src/app/todo-app/todo-app.component.html @@ -1,12 +1,14 @@ - + - - - \ No newline at end of file + + diff --git a/src/app/todo-app/todo-app.component.ts b/src/app/todo-app/todo-app.component.ts index cc42271..7a291d2 100644 --- a/src/app/todo-app/todo-app.component.ts +++ b/src/app/todo-app/todo-app.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import {TodoItem} from '../model/todo-item'; +import { TodoItem } from '../model/todo-item'; import { element } from 'protractor'; import { TodoService } from '../todo.service'; /** */ @@ -8,11 +8,9 @@ import { TodoService } from '../todo.service'; templateUrl: './todo-app.component.html', styleUrls: ['./todo-app.component.scss'], }) -export class TodoAppComponent { - - constructor( - private service: TodoService - ) {} +export class TodoAppComponent { + constructor(private service: TodoService) {} + itemToEdit: TodoItem = null; getList() { return this.service.list; @@ -23,7 +21,13 @@ export class TodoAppComponent { onItemStateChanged(item: TodoItem) { item.toggleCompleted(); } - onTodoItemCreated(task) { - this.service.add(task) + onTodoItemCreated(task: TodoItem) { + this.service.add(task); + } + onTodoItemUpdated(task: TodoItem) { + this.service.update(task); + } + onItemEdit(item: TodoItem) { + this.itemToEdit = item; } } diff --git a/src/app/todo-form/todo-form.component.html b/src/app/todo-form/todo-form.component.html index b02ebd5..8c9786b 100644 --- a/src/app/todo-form/todo-form.component.html +++ b/src/app/todo-form/todo-form.component.html @@ -2,6 +2,6 @@ diff --git a/src/app/todo-form/todo-form.component.ts b/src/app/todo-form/todo-form.component.ts index a300660..555b90a 100644 --- a/src/app/todo-form/todo-form.component.ts +++ b/src/app/todo-form/todo-form.component.ts @@ -1,4 +1,10 @@ -import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { + Component, + Output, + EventEmitter, + Input, + OnChanges, +} from '@angular/core'; import { TodoItem } from '../model/todo-item'; import { FormGroup, FormControl, Validators } from '@angular/forms'; import * as customValidators from '../custom-validators'; @@ -8,12 +14,23 @@ import * as customValidators from '../custom-validators'; templateUrl: './todo-form.component.html', styleUrls: ['./todo-form.component.scss'], }) -export class TodoFormComponent { +export class TodoFormComponent implements OnChanges { + @Input() itemToEdit: TodoItem = null; + @Output() add = new EventEmitter(); + @Output() update = new EventEmitter(); + + editMode = false; taskForm = new FormGroup({ description: new FormControl('', [Validators.required]), url: new FormControl('', [Validators.required, customValidators.isUrl]), }); - @Output() add = new EventEmitter(); + + ngOnChanges(): void { + if (this.itemToEdit) { + this.editMode = true; + this.taskForm.patchValue(this.itemToEdit); + } + } get description() { return this.taskForm.get('description'); @@ -23,11 +40,20 @@ export class TodoFormComponent { } onSubmit() { - let task = new TodoItem(); + const task = new TodoItem(); task.description = this.description.value; task.url = this.url.value; - task.isCompleted = false; - this.add.emit(task); + + if (this.editMode) { + task.id = this.itemToEdit.id; + task.isCompleted = this.itemToEdit.isCompleted; + this.update.emit(task); + } else { + task.isCompleted = false; + this.add.emit(task); + } + this.itemToEdit = null; + this.editMode = false; this.taskForm.reset(); } } diff --git a/src/app/todo-list/todo-list.component.html b/src/app/todo-list/todo-list.component.html index e7f55eb..f8be0b4 100644 --- a/src/app/todo-list/todo-list.component.html +++ b/src/app/todo-list/todo-list.component.html @@ -4,8 +4,9 @@ [class.completed]="task.isCompleted" class="list-item" > - {{ task.description }} {{ task.url }} + {{ task.description }} {{ task.url }} {{task.id}} + diff --git a/src/app/todo-list/todo-list.component.ts b/src/app/todo-list/todo-list.component.ts index fc5d8f0..829eeb3 100644 --- a/src/app/todo-list/todo-list.component.ts +++ b/src/app/todo-list/todo-list.component.ts @@ -1,26 +1,26 @@ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { TodoItem } from '../model/todo-item'; @Component({ selector: 'app-todo-list', templateUrl: './todo-list.component.html', - styleUrls: ['./todo-list.component.scss'] + styleUrls: ['./todo-list.component.scss'], }) -export class TodoListComponent implements OnInit { - @Input() list: any[]; - @Output() itemRemoved = new EventEmitter(); - @Output() itemStateChanged = new EventEmitter(); - constructor() { } +export class TodoListComponent{ + @Input() list: TodoItem[]; + @Output() itemRemoved = new EventEmitter(); + @Output() itemStateChanged = new EventEmitter(); + @Output() itemEdit = new EventEmitter(); - ngOnInit() { - } - removeItem(id) { + removeItem(id: number) { this.itemRemoved.emit(id); } - completeTask(item:TodoItem) { + completeTask(item: TodoItem) { this.itemStateChanged.emit(item); - } -} \ No newline at end of file + editItem(item: TodoItem) { + this.itemEdit.emit(item); + } +} diff --git a/src/app/todo.service.ts b/src/app/todo.service.ts index 9bba8cc..c2aa032 100644 --- a/src/app/todo.service.ts +++ b/src/app/todo.service.ts @@ -1,16 +1,13 @@ import { Injectable } from '@angular/core'; -import { LocalStorageService } from './local-storage.service'; +import { TodoItem } from './model/todo-item'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class TodoService { - list = []; lastItemId = 0; - constructor(private storage: LocalStorageService) { } - add(task) { const id = this.lastItemId; task.id = id; @@ -18,20 +15,23 @@ export class TodoService { this.lastItemId += 10; } + update(task: TodoItem) { + console.log(task); + const index = this.list.findIndex((element) => element.id === task.id); + console.log(index); + this.list.splice(index, 1, task); + console.log(this.list); + } + remove(id) { const index = this.list.findIndex((element) => element.id === id); this.list.splice(index, 1); } incompletedSize() { - return this.list.filter(item => !item.isCompleted).length; - + return this.list.filter((item) => !item.isCompleted).length; } completedSize() { - return this.list.filter(item => item.isCompleted).length ; - } - - getName() { - return 'TodoService 123' + this.storage.getName(); + return this.list.filter((item) => item.isCompleted).length; } } From 5ff835fc32811f3238b4c9cf426a37688c8e25c3 Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Mon, 24 Aug 2020 23:22:14 -0300 Subject: [PATCH 05/12] Clean unused code and unneeded files --- src/app/app.component.html | 24 -------------------- src/app/local-storage.service.spec.ts | 16 ------------- src/app/local-storage.service.ts | 12 ---------- src/app/model/todo-item.ts | 2 +- src/app/stats/stats.component.html | 7 ++---- src/app/stats/stats.component.ts | 17 +++++++------- src/app/todo-footer/todo-footer.component.ts | 16 ++++--------- 7 files changed, 16 insertions(+), 78 deletions(-) delete mode 100644 src/app/local-storage.service.spec.ts delete mode 100644 src/app/local-storage.service.ts diff --git a/src/app/app.component.html b/src/app/app.component.html index f348a5a..c34ed49 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -4,27 +4,3 @@
    - -
    -
    -
    -
    - - - - -
    -
    -
    - Valido {{profileForm.valid}} - -
    - Touched {{profileForm.touched}} -
    -
    diff --git a/src/app/local-storage.service.spec.ts b/src/app/local-storage.service.spec.ts deleted file mode 100644 index ba1dbd4..0000000 --- a/src/app/local-storage.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { LocalStorageService } from './local-storage.service'; - -describe('LocalStorageService', () => { - let service: LocalStorageService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(LocalStorageService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/app/local-storage.service.ts b/src/app/local-storage.service.ts deleted file mode 100644 index 204c7fc..0000000 --- a/src/app/local-storage.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class LocalStorageService { - // https://developer.mozilla.org/es/docs/Web/API/Window/localStorage - constructor() { } - getName() { - return 'LocalStorageService' - } -} diff --git a/src/app/model/todo-item.ts b/src/app/model/todo-item.ts index f4d4be4..7738327 100644 --- a/src/app/model/todo-item.ts +++ b/src/app/model/todo-item.ts @@ -2,7 +2,7 @@ export class TodoItem { id: number; description: string; url: string; - isCompleted: boolean = false; + isCompleted = false; toggleCompleted() { this.isCompleted = !this.isCompleted; diff --git a/src/app/stats/stats.component.html b/src/app/stats/stats.component.html index b599e29..6f7d960 100644 --- a/src/app/stats/stats.component.html +++ b/src/app/stats/stats.component.html @@ -1,6 +1,3 @@ -
    -{{completedPercentage()}} % +
    + {{ completedPercentage() }}%
    diff --git a/src/app/stats/stats.component.ts b/src/app/stats/stats.component.ts index 1ec80aa..99ed0c2 100644 --- a/src/app/stats/stats.component.ts +++ b/src/app/stats/stats.component.ts @@ -4,17 +4,16 @@ import { TodoService } from '../todo.service'; @Component({ selector: 'app-stats', templateUrl: './stats.component.html', - styleUrls: ['./stats.component.scss'] + styleUrls: ['./stats.component.scss'], }) -export class StatsComponent implements OnInit { +export class StatsComponent { + constructor(private service: TodoService) {} - constructor( - private service: TodoService - ) { } - - ngOnInit(): void { - } completedPercentage() { - return Math.round(this.service.completedSize() / this.service.list.length * 100) || 0 + return ( + Math.round( + (this.service.completedSize() / this.service.list.length) * 100 + ) || 0 + ); } } diff --git a/src/app/todo-footer/todo-footer.component.ts b/src/app/todo-footer/todo-footer.component.ts index 6e1af04..0197da7 100644 --- a/src/app/todo-footer/todo-footer.component.ts +++ b/src/app/todo-footer/todo-footer.component.ts @@ -5,26 +5,20 @@ import { TodoService } from '../todo.service'; @Component({ selector: 'app-todo-footer', templateUrl: './todo-footer.component.html', - styleUrls: ['./todo-footer.component.scss'] + styleUrls: ['./todo-footer.component.scss'], }) -export class TodoFooterComponent implements OnInit { +export class TodoFooterComponent { countTodo = 0; countCompleted = 0; @Input() list; - constructor( - private service: TodoService - ) { } + constructor(private service: TodoService) {} - ngOnInit() { - - } incompletedSize() { - this.countTodo = this.service.incompletedSize() + this.countTodo = this.service.incompletedSize(); return this.countTodo; } completedSize() { - this.countCompleted =this.service.completedSize() + this.countCompleted = this.service.completedSize(); return this.countCompleted; } - } From 1cc694e2066a4aeaf22e9c745d6bddb8d3131bac Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Tue, 25 Aug 2020 00:05:57 -0300 Subject: [PATCH 06/12] Add clearer css directives and html in todo form --- src/app/app.component.ts | 26 ---------------------- src/app/todo-form/todo-form.component.html | 8 +++++-- src/app/todo-form/todo-form.component.scss | 4 ++-- src/app/todo-list/todo-list.component.html | 2 +- src/app/todo.service.ts | 3 --- 5 files changed, 9 insertions(+), 34 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0f24e4a..0dfc324 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -14,30 +14,4 @@ import { }) export class AppComponent { title = 'Todo'; - profileForm = new FormGroup({ - firstName: new FormControl('', [ - Validators.required, - Validators.maxLength(10), - ]), - lastName: new FormControl('', [Validators.required, StartsWithAValidator]), - }); - constructor() { - this.profileForm.valueChanges.subscribe((value) => console.log(value)); - } - - onSubmit() { - console.log(this.profileForm.value); - } - initialize() { - this.profileForm.reset(); - } } - -export const StartsWithAValidator = (): ValidatorFn => ( - control: AbstractControl -): { [key: string]: any } | null => { - if (!control.value.startsWith('A')) { - return { startsWithA: false }; - } - return null; -}; diff --git a/src/app/todo-form/todo-form.component.html b/src/app/todo-form/todo-form.component.html index 8c9786b..09e6a18 100644 --- a/src/app/todo-form/todo-form.component.html +++ b/src/app/todo-form/todo-form.component.html @@ -1,7 +1,11 @@
    -
    diff --git a/src/app/todo-form/todo-form.component.scss b/src/app/todo-form/todo-form.component.scss index ce87b8f..d40d265 100644 --- a/src/app/todo-form/todo-form.component.scss +++ b/src/app/todo-form/todo-form.component.scss @@ -2,10 +2,10 @@ width: 80%; } -input.ng-valid { +input.ng-valid.ng-touched { border: 3px solid green; } -input.ng-invalid { +input.ng-invalid.ng-touched { border: 3px solid red; } diff --git a/src/app/todo-list/todo-list.component.html b/src/app/todo-list/todo-list.component.html index f8be0b4..f7b3505 100644 --- a/src/app/todo-list/todo-list.component.html +++ b/src/app/todo-list/todo-list.component.html @@ -4,7 +4,7 @@ [class.completed]="task.isCompleted" class="list-item" > - {{ task.description }} {{ task.url }} {{task.id}} + {{ task.description }} - {{ task.url }} diff --git a/src/app/todo.service.ts b/src/app/todo.service.ts index c2aa032..579ef4a 100644 --- a/src/app/todo.service.ts +++ b/src/app/todo.service.ts @@ -16,11 +16,8 @@ export class TodoService { } update(task: TodoItem) { - console.log(task); const index = this.list.findIndex((element) => element.id === task.id); - console.log(index); this.list.splice(index, 1, task); - console.log(this.list); } remove(id) { From 33327b16f42f72691724f7ff3e18083f1b6c135c Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Tue, 25 Aug 2020 17:41:38 -0300 Subject: [PATCH 07/12] . --- src/app/todo-app/todo-app.component.ts | 3 +-- src/app/todo-footer/todo-footer.component.ts | 1 - src/app/todo-list/todo-list.component.ts | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/todo-app/todo-app.component.ts b/src/app/todo-app/todo-app.component.ts index 7a291d2..5b82a0b 100644 --- a/src/app/todo-app/todo-app.component.ts +++ b/src/app/todo-app/todo-app.component.ts @@ -1,6 +1,5 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { TodoItem } from '../model/todo-item'; -import { element } from 'protractor'; import { TodoService } from '../todo.service'; /** */ @Component({ diff --git a/src/app/todo-footer/todo-footer.component.ts b/src/app/todo-footer/todo-footer.component.ts index 0197da7..2e58acb 100644 --- a/src/app/todo-footer/todo-footer.component.ts +++ b/src/app/todo-footer/todo-footer.component.ts @@ -1,5 +1,4 @@ import { Component, OnInit, Input } from '@angular/core'; -import { TodoItem } from '../model/todo-item'; import { TodoService } from '../todo.service'; @Component({ diff --git a/src/app/todo-list/todo-list.component.ts b/src/app/todo-list/todo-list.component.ts index 829eeb3..25c92e7 100644 --- a/src/app/todo-list/todo-list.component.ts +++ b/src/app/todo-list/todo-list.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter } from '@angular/core'; import { TodoItem } from '../model/todo-item'; @Component({ From a13cc2c934b669f81c570aaae13cb7553163ce23 Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Tue, 25 Aug 2020 17:55:41 -0300 Subject: [PATCH 08/12] Setup and test angular material --- angular.json | 2 ++ package-lock.json | 21 +++++++++++++++++++++ package.json | 2 ++ src/app/app.component.html | 1 + src/app/app.module.ts | 4 +++- src/index.html | 4 +++- src/styles.scss | 3 +++ 7 files changed, 35 insertions(+), 2 deletions(-) diff --git a/angular.json b/angular.json index 2ecf625..aa8bc82 100644 --- a/angular.json +++ b/angular.json @@ -28,6 +28,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/bootstrap/dist/css/bootstrap.min.css", "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", "src/styles.scss" @@ -94,6 +95,7 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/bootstrap/dist/css/bootstrap.min.css", "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", "src/styles.scss" diff --git a/package-lock.json b/package-lock.json index 90e9696..aa3845c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -199,6 +199,22 @@ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.1.tgz", "integrity": "sha512-IvKv8sV0ymbzDEX2ZLW+F6nOTQqDYallHexuzRVT9txvNE8TNHyySvLcyC5dTmX9fj9LA72NZ6nFyhxq0LFvtQ==" }, + "@angular/cdk": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.4.tgz", + "integrity": "sha512-iw2+qHMXHYVC6K/fttHeNHIieSKiTEodVutZoOEcBu9rmRTGbLB26V/CRsfIRmA1RBk+uFYWc6UQZnMC3RdnJQ==", + "requires": { + "parse5": "^5.0.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + } + } + }, "@angular/cli": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.1.1.tgz", @@ -470,6 +486,11 @@ "integrity": "sha512-T+/0X2VnmgW/vzynqYTVv29qtebNvrCB/yJqtNIlqXvBjcB8XRRwZPDZvRyl5BiwEPSsJnjdRFNH9krQHxYp+g==", "dev": true }, + "@angular/material": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.2.4.tgz", + "integrity": "sha512-LkoTXE6B0slvMhvfZDdPWaz4yaYLkaAp5VSPunI9pxGsPxzqEV9e210wC1/sjG/76Nk8Ep7/2z9XKac8Q9bMwA==" + }, "@angular/platform-browser": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.1.1.tgz", diff --git a/package.json b/package.json index c512cf5..32704fc 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,12 @@ "private": true, "dependencies": { "@angular/animations": "~9.1.1", + "@angular/cdk": "^9.2.4", "@angular/common": "~9.1.1", "@angular/compiler": "~9.1.1", "@angular/core": "~9.1.1", "@angular/forms": "~9.1.1", + "@angular/material": "^9.2.4", "@angular/platform-browser": "~9.1.1", "@angular/platform-browser-dynamic": "~9.1.1", "@angular/router": "~9.1.1", diff --git a/src/app/app.component.html b/src/app/app.component.html index c34ed49..7598e8e 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,3 +1,4 @@ + diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2b82464..1b76852 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -11,6 +11,7 @@ import { TodoFooterComponent } from './todo-footer/todo-footer.component'; import { TodoService } from './todo.service'; import { StatsComponent } from './stats/stats.component'; import { ReactiveFormsModule } from '@angular/forms'; +import { MatSliderModule } from '@angular/material/slider'; @NgModule({ declarations: [ @@ -25,7 +26,8 @@ import { ReactiveFormsModule } from '@angular/forms'; BrowserModule, AppRoutingModule, BrowserAnimationsModule, - ReactiveFormsModule + ReactiveFormsModule, + MatSliderModule ], providers: [], bootstrap: [AppComponent] diff --git a/src/index.html b/src/index.html index 62a7817..28d80b3 100644 --- a/src/index.html +++ b/src/index.html @@ -6,8 +6,10 @@ + + - + diff --git a/src/styles.scss b/src/styles.scss index afb57e2..66a9bc1 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -2,3 +2,6 @@ .btn { margin: 0.5rem; } + +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } From dab81a6889e7a6f0fdffe77b033999c0fb1ad27d Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Tue, 25 Aug 2020 19:49:36 -0300 Subject: [PATCH 09/12] Configure mat-table in todo-list component --- src/app/app.component.html | 1 - src/app/app.module.ts | 15 ++++--- src/app/todo-app/todo-app.component.html | 2 +- src/app/todo-app/todo-app.component.ts | 4 ++ src/app/todo-list/todo-list.component.html | 46 +++++++++++++++------- src/app/todo-list/todo-list.component.scss | 2 +- src/app/todo-list/todo-list.component.ts | 8 +++- 7 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index 7598e8e..c34ed49 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,4 +1,3 @@ - diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1b76852..0fc7586 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -8,10 +8,12 @@ import { TodoAppComponent } from './todo-app/todo-app.component'; import { TodoFormComponent } from './todo-form/todo-form.component'; import { TodoListComponent } from './todo-list/todo-list.component'; import { TodoFooterComponent } from './todo-footer/todo-footer.component'; -import { TodoService } from './todo.service'; import { StatsComponent } from './stats/stats.component'; import { ReactiveFormsModule } from '@angular/forms'; -import { MatSliderModule } from '@angular/material/slider'; +import { MatTableModule } from '@angular/material/table'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatIconModule } from '@angular/material/icon'; @NgModule({ declarations: [ @@ -27,9 +29,12 @@ import { MatSliderModule } from '@angular/material/slider'; AppRoutingModule, BrowserAnimationsModule, ReactiveFormsModule, - MatSliderModule + MatTableModule, + MatButtonModule, + MatCheckboxModule, + MatIconModule, ], providers: [], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/src/app/todo-app/todo-app.component.html b/src/app/todo-app/todo-app.component.html index 37378f1..eaa8e4b 100644 --- a/src/app/todo-app/todo-app.component.html +++ b/src/app/todo-app/todo-app.component.html @@ -4,7 +4,7 @@ (update)="onTodoItemUpdated($event)" > (this.service.list); + } onTodoItemRemoved(id) { this.service.remove(id); } diff --git a/src/app/todo-list/todo-list.component.html b/src/app/todo-list/todo-list.component.html index f7b3505..2d67a74 100644 --- a/src/app/todo-list/todo-list.component.html +++ b/src/app/todo-list/todo-list.component.html @@ -1,14 +1,32 @@ -
      -
    • - {{ task.description }} - {{ task.url }} - - - - - -
    • -
    + + + Completed + + + + + + Description + {{ task.description }} + + + Url + {{ task.url }} + + + Actions + + + + + + + + diff --git a/src/app/todo-list/todo-list.component.scss b/src/app/todo-list/todo-list.component.scss index 0096849..0f0e065 100644 --- a/src/app/todo-list/todo-list.component.scss +++ b/src/app/todo-list/todo-list.component.scss @@ -1,5 +1,5 @@ .completed { - background-color: greenyellow; + background-color: greenyellow; } .list { padding: 0; diff --git a/src/app/todo-list/todo-list.component.ts b/src/app/todo-list/todo-list.component.ts index 25c92e7..7ac7a12 100644 --- a/src/app/todo-list/todo-list.component.ts +++ b/src/app/todo-list/todo-list.component.ts @@ -1,17 +1,21 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; import { TodoItem } from '../model/todo-item'; +import { MatTableDataSource } from '@angular/material/table'; @Component({ selector: 'app-todo-list', templateUrl: './todo-list.component.html', styleUrls: ['./todo-list.component.scss'], }) -export class TodoListComponent{ - @Input() list: TodoItem[]; +export class TodoListComponent { + // https://stackoverflow.com/questions/49141809/angular-input-change-detection-performance-with-mat-table-data-source + @Input() dataSource: MatTableDataSource; @Output() itemRemoved = new EventEmitter(); @Output() itemStateChanged = new EventEmitter(); @Output() itemEdit = new EventEmitter(); + columnsToDisplay = ['isCompleted', 'description', 'url', 'actions']; + removeItem(id: number) { this.itemRemoved.emit(id); } From 23084cd03582e10f8e9bcefee498c0ea34628d9d Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Tue, 25 Aug 2020 22:27:23 -0300 Subject: [PATCH 10/12] Implement mat-progress-bar --- src/app/app.module.ts | 3 ++- src/app/stats/stats.component.html | 4 +--- src/app/stats/stats.component.scss | 6 ------ src/app/todo-app/todo-app.component.html | 3 ++- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0fc7586..5987d55 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,5 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; - import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -14,6 +13,7 @@ import { MatTableModule } from '@angular/material/table'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; @NgModule({ declarations: [ @@ -33,6 +33,7 @@ import { MatIconModule } from '@angular/material/icon'; MatButtonModule, MatCheckboxModule, MatIconModule, + MatProgressBarModule, ], providers: [], bootstrap: [AppComponent], diff --git a/src/app/stats/stats.component.html b/src/app/stats/stats.component.html index 6f7d960..05aa977 100644 --- a/src/app/stats/stats.component.html +++ b/src/app/stats/stats.component.html @@ -1,3 +1 @@ -
    - {{ completedPercentage() }}% -
    + diff --git a/src/app/stats/stats.component.scss b/src/app/stats/stats.component.scss index 3543841..e69de29 100644 --- a/src/app/stats/stats.component.scss +++ b/src/app/stats/stats.component.scss @@ -1,6 +0,0 @@ -.progressbar { - background-color: green; - height: 50px; - font-size: x-large; - transition: width 0.3s; -} diff --git a/src/app/todo-app/todo-app.component.html b/src/app/todo-app/todo-app.component.html index eaa8e4b..3c1288b 100644 --- a/src/app/todo-app/todo-app.component.html +++ b/src/app/todo-app/todo-app.component.html @@ -10,5 +10,6 @@ (itemEdit)="onItemEdit($event)" >
    - + + From 5965754de52bc37d2848cbf9f7469c5e51189296 Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Tue, 25 Aug 2020 23:30:39 -0300 Subject: [PATCH 11/12] Use mat-form-field and configure a custom error state matcher --- src/app/app.module.ts | 4 ++++ src/app/custom-error-matcher.ts | 8 ++++++++ src/app/todo-form/todo-form.component.html | 23 +++++++++++++++++++--- src/app/todo-form/todo-form.component.scss | 10 +++------- src/app/todo-form/todo-form.component.ts | 3 +++ 5 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 src/app/custom-error-matcher.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5987d55..19ea5af 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -14,6 +14,8 @@ import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; @NgModule({ declarations: [ @@ -34,6 +36,8 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; MatCheckboxModule, MatIconModule, MatProgressBarModule, + MatFormFieldModule, + MatInputModule, ], providers: [], bootstrap: [AppComponent], diff --git a/src/app/custom-error-matcher.ts b/src/app/custom-error-matcher.ts new file mode 100644 index 0000000..d824f91 --- /dev/null +++ b/src/app/custom-error-matcher.ts @@ -0,0 +1,8 @@ +import { FormControl, NgForm, FormGroupDirective } from '@angular/forms'; +import { ErrorStateMatcher } from '@angular/material/core'; + +export class CustomErrorStateMatcher implements ErrorStateMatcher { + isErrorState(control: FormControl, form: NgForm | FormGroupDirective | null) { + return control && control.invalid && control.touched; + } +} diff --git a/src/app/todo-form/todo-form.component.html b/src/app/todo-form/todo-form.component.html index 09e6a18..68f9e0a 100644 --- a/src/app/todo-form/todo-form.component.html +++ b/src/app/todo-form/todo-form.component.html @@ -1,10 +1,27 @@
    - - + + Description + + + + Url + + diff --git a/src/app/todo-form/todo-form.component.scss b/src/app/todo-form/todo-form.component.scss index d40d265..c346e57 100644 --- a/src/app/todo-form/todo-form.component.scss +++ b/src/app/todo-form/todo-form.component.scss @@ -1,11 +1,7 @@ -.add-todo { +.form-todo { width: 80%; } -input.ng-valid.ng-touched { - border: 3px solid green; -} - -input.ng-invalid.ng-touched { - border: 3px solid red; +#btn-submit { + margin-left: 1em; } diff --git a/src/app/todo-form/todo-form.component.ts b/src/app/todo-form/todo-form.component.ts index 555b90a..14cc97e 100644 --- a/src/app/todo-form/todo-form.component.ts +++ b/src/app/todo-form/todo-form.component.ts @@ -8,6 +8,7 @@ import { import { TodoItem } from '../model/todo-item'; import { FormGroup, FormControl, Validators } from '@angular/forms'; import * as customValidators from '../custom-validators'; +import { CustomErrorStateMatcher } from '../custom-error-matcher'; @Component({ selector: 'app-todo-form', @@ -19,6 +20,8 @@ export class TodoFormComponent implements OnChanges { @Output() add = new EventEmitter(); @Output() update = new EventEmitter(); + errorMatcher = new CustomErrorStateMatcher() + editMode = false; taskForm = new FormGroup({ description: new FormControl('', [Validators.required]), From c8663d05c16394afecd2a46c7eb31afd61556139 Mon Sep 17 00:00:00 2001 From: Alejandro Recalde Date: Tue, 25 Aug 2020 23:40:03 -0300 Subject: [PATCH 12/12] Fix some css margins --- src/app/todo-footer/todo-footer.component.html | 6 ++++-- src/app/todo-footer/todo-footer.component.scss | 7 +++++++ src/app/todo-form/todo-form.component.html | 4 ++-- src/app/todo-form/todo-form.component.scss | 6 +++++- src/app/todo-list/todo-list.component.scss | 15 --------------- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/app/todo-footer/todo-footer.component.html b/src/app/todo-footer/todo-footer.component.html index a0f7423..fcebda2 100644 --- a/src/app/todo-footer/todo-footer.component.html +++ b/src/app/todo-footer/todo-footer.component.html @@ -1,2 +1,4 @@ -

    Tasks Completed {{completedSize()}}

    -

    Tasks To do {{incompletedSize()}}

    + diff --git a/src/app/todo-footer/todo-footer.component.scss b/src/app/todo-footer/todo-footer.component.scss index e69de29..6155f23 100644 --- a/src/app/todo-footer/todo-footer.component.scss +++ b/src/app/todo-footer/todo-footer.component.scss @@ -0,0 +1,7 @@ +.footer { + margin-top: 0.5em; +} + +h2 { + margin: 0; +} diff --git a/src/app/todo-form/todo-form.component.html b/src/app/todo-form/todo-form.component.html index 68f9e0a..9fed148 100644 --- a/src/app/todo-form/todo-form.component.html +++ b/src/app/todo-form/todo-form.component.html @@ -1,5 +1,5 @@ - + Description - + Url