Key Files:
- todo-form.module.ts
- todo-form.component.ts
- todo-form.component.html
- local-storage.service.ts
| import { Injectable } from '@angular/core'; | |
| import { EMPTY, Observable, of, Subscriber } from 'rxjs'; | |
| import Todo from '../model/todo.model'; | |
| @Injectable({ | |
| providedIn: 'root' | |
| }) | |
| export class LocalStorageService { | |
| private get state(): Storage { | |
| return window.localStorage; | |
| } | |
| private get generateTodoKey(): string { | |
| return Math.random().toString(36).slice(2) + Date.now(); | |
| } | |
| getStateItems() { | |
| const entries = Object.entries(this.state); | |
| return of(entries); | |
| } | |
| getSingleItem(key: string): Observable<Todo | null> { | |
| const item = this.state.getItem(key); | |
| if (!item) return of(null); | |
| const todo: Todo = JSON.parse(item); | |
| return of(todo); | |
| } | |
| saveToState(value: Todo): Observable<unknown> { | |
| return new Observable((observer: Subscriber<unknown>) => { | |
| this.state.setItem(this.generateTodoKey, JSON.stringify(value)); | |
| observer.next(); | |
| observer.complete(); | |
| }) | |
| } | |
| deleteStateItem(key: string): Observable<null> { | |
| this.state.removeItem(key); | |
| return EMPTY; | |
| } | |
| deleteAllItems(): Observable<null> { | |
| this.state.clear(); | |
| return EMPTY; | |
| } | |
| } |
| <form [formGroup]="todoForm" (ngSubmit)="onSubmit()"> | |
| <div class="title-wrapper"> | |
| <input formControlName="title" type="text" /> | |
| <div class="error-message" *ngIf="isInvalidTitle">Please fix your input!</div> | |
| </div> | |
| <div class="description-wrapper"> | |
| <textarea formControlName="description"></textarea> | |
| <div class="error-message" *ngIf="isInvalidDescription">Maximum number of characters is 300!</div> | |
| </div> | |
| <button class="submit-btn" type="submit">Submit</button> | |
| </form> |
| import { Component, Input, OnInit } from '@angular/core'; | |
| import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | |
| import { filter, take } from 'rxjs'; | |
| import { LocalStorageService } from 'src/app/services/local-storage.service'; | |
| @Component({ | |
| selector: 'app-todo-form', | |
| templateUrl: './todo-form.component.html', | |
| styleUrls: ['./todo-form.component.scss'], | |
| }) | |
| export class TodoFormComponent implements OnInit { | |
| todoForm!: FormGroup; | |
| @Input() | |
| todoId!: string; | |
| constructor(private fb: FormBuilder, private service: LocalStorageService) {} | |
| ngOnInit(): void { | |
| this.setupForm(); | |
| this.populateForm() | |
| } | |
| get isInvalidTitle(): boolean { | |
| const titleControl = this.todoForm.get('title'); | |
| if (!titleControl) return false; | |
| return titleControl.invalid && (titleControl.dirty || titleControl.touched); | |
| } | |
| get isInvalidDescription(): boolean { | |
| const descriptionControl = this.todoForm.get('description'); | |
| if (!descriptionControl) return false; | |
| return descriptionControl.invalid && (descriptionControl.dirty || descriptionControl.touched); | |
| } | |
| private populateForm() { | |
| if (!this.todoId) return; | |
| this.service.getSingleItem(this.todoId) | |
| .pipe( | |
| filter(data => !!data), | |
| take(1) | |
| ) | |
| .subscribe(savedItem => { | |
| this.todoForm.patchValue({ | |
| title: savedItem?.title, | |
| description: savedItem?.description | |
| }) | |
| }) | |
| } | |
| private setupForm(): void { | |
| this.todoForm = this.fb.group({ | |
| title: ['', [Validators.required, Validators.maxLength(100)]], | |
| description: ['', [Validators.maxLength(300)]], | |
| }); | |
| } | |
| private triggerValidationOnSubmit() { | |
| Object.keys(this.todoForm.controls) | |
| .forEach((field: string) => { | |
| const control = this.todoForm.get(field); | |
| control?.markAsTouched({ onlySelf: true }); | |
| }); | |
| } | |
| onSubmit(): void { | |
| if (!this.todoForm.valid) { | |
| this.triggerValidationOnSubmit(); | |
| return | |
| } | |
| console.log('done') | |
| // this.service.saveToState(this.todoForm.value).subscribe(); | |
| } | |
| } |
| import { NgModule } from '@angular/core'; | |
| import { TodoFormComponent } from './todo-form.component'; | |
| import { ReactiveFormsModule } from '@angular/forms'; | |
| import { CommonModule } from '@angular/common'; | |
| @NgModule({ | |
| declarations: [ | |
| TodoFormComponent | |
| ], | |
| imports: [ | |
| CommonModule, | |
| ReactiveFormsModule | |
| ], | |
| exports: [ | |
| TodoFormComponent | |
| ] | |
| }) | |
| export class TodoFormModule { } |