From 3b5205e124c5a6ef5df5e771c2bd65b4df8c85ea Mon Sep 17 00:00:00 2001 From: Gabriel De Los Rios Date: Sun, 21 Dec 2025 00:10:35 -0300 Subject: [PATCH] feat: add notification to contact response --- .../services/ResponseStateNotificatio.spec.ts | 53 +++++++++++++++++++ src/app/services/ResponseStateNotificatio.ts | 26 +++++++++ src/app/services/contact.service.ts | 42 +++++++++++---- src/app/strings.ts | 4 ++ 4 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 src/app/services/ResponseStateNotificatio.spec.ts create mode 100644 src/app/services/ResponseStateNotificatio.ts diff --git a/src/app/services/ResponseStateNotificatio.spec.ts b/src/app/services/ResponseStateNotificatio.spec.ts new file mode 100644 index 0000000..b0bde56 --- /dev/null +++ b/src/app/services/ResponseStateNotificatio.spec.ts @@ -0,0 +1,53 @@ +import { TestBed } from '@angular/core/testing'; +import { ResponseStateNotification } from './ResponseStateNotificatio'; +import { Notifier } from './notifier'; +import { catchError, EMPTY, of, throwError } from 'rxjs'; +import { Notification } from '../models/Notification'; + +describe('ResponseStateNotificatio', () => { + let service: ResponseStateNotification; + let notifier: jasmine.SpyObj; + + let NOTIFICATION_MOCK: Notification; + beforeEach(() => { + notifier = jasmine.createSpyObj(Notifier.name, ['notify']); + NOTIFICATION_MOCK = new Notification('mock', 'success'); + + TestBed.configureTestingModule({ + providers: [{ provide: Notifier, useValue: notifier }], + }); + service = TestBed.inject(ResponseStateNotification); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should notify success if observable has no errors', () => { + service + .handleResponse( + of({ + data: null, + message: 'success', + success: true, + }), + NOTIFICATION_MOCK.message, + NOTIFICATION_MOCK.message + ) + .subscribe(); + expect(notifier.notify).toHaveBeenCalledOnceWith(NOTIFICATION_MOCK); + }); + + it('should notify error if observable throws error', () => { + service + .handleResponse( + throwError(() => new Error('Stream errored!')), + NOTIFICATION_MOCK.message, + NOTIFICATION_MOCK.message + ) + .pipe(catchError(() => of(EMPTY))) + .subscribe(); + NOTIFICATION_MOCK.type = 'error'; + expect(notifier.notify).toHaveBeenCalledOnceWith(NOTIFICATION_MOCK); + }); +}); diff --git a/src/app/services/ResponseStateNotificatio.ts b/src/app/services/ResponseStateNotificatio.ts new file mode 100644 index 0000000..27d1e23 --- /dev/null +++ b/src/app/services/ResponseStateNotificatio.ts @@ -0,0 +1,26 @@ +import { catchError, Observable, tap, throwError } from 'rxjs'; +import { Response } from '../models/Response'; +import { Notification } from '../models/Notification'; +import { inject, Injectable } from '@angular/core'; +import { Notifier } from './notifier'; + +@Injectable({ + providedIn: 'root', +}) +export class ResponseStateNotification { + private readonly notifier = inject(Notifier); + + handleResponse( + r: Observable>, + success: string, + error: string + ): Observable> { + return r.pipe( + tap(() => this.notifier.notify(new Notification(success, 'success'))), + catchError((err) => { + this.notifier.notify(new Notification(error, 'error')); + return throwError(() => err); + }) + ); + } +} diff --git a/src/app/services/contact.service.ts b/src/app/services/contact.service.ts index 8b1a277..6b2143d 100644 --- a/src/app/services/contact.service.ts +++ b/src/app/services/contact.service.ts @@ -4,19 +4,29 @@ import { environment } from '../../environments/environment'; import { ContactDTO } from '../models/ContactDTO'; import { Response } from '../models/Response'; import { BehaviorSubject, map, switchMap, tap } from 'rxjs'; +import { ResponseStateNotification } from './ResponseStateNotificatio'; +import { strings } from '../strings'; @Injectable({ providedIn: 'root', }) export class ContactService { private readonly httpClient = inject(HttpClient); + private readonly responseStateNotification = inject(ResponseStateNotification); private readonly contacts = new BehaviorSubject([]); + readonly contacts$ = this.contacts.asObservable(); delete(id: number) { - return this.httpClient - .delete>(`${environment.apiUrl}/contacts/${id}`) - .pipe(switchMap(() => this.getAll())); + return this.httpClient.delete>(`${environment.apiUrl}/contacts/${id}`).pipe( + (r) => + this.responseStateNotification.handleResponse( + r, + strings.deleteContactSuccessNotification, + strings.errorNotification + ), + switchMap(() => this.getAll()) + ); } findById(id: string) { @@ -33,15 +43,27 @@ export class ContactService { } save(contact: ContactDTO) { - return this.httpClient - .post>(environment.apiUrl + '/contacts', contact) - .pipe(switchMap(() => this.getAll())); + return this.httpClient.post(environment.apiUrl + '/contacts', contact).pipe( + (r) => + this.responseStateNotification.handleResponse( + r, + strings.createContactSuccessNotification, + strings.errorNotification + ), + + switchMap(() => this.getAll()) + ); } update(contact: ContactDTO) { - return this.httpClient.put>( - `${environment.apiUrl}/contacts/${contact.id}`, - contact - ); + return this.httpClient + .put>(`${environment.apiUrl}/contacts/${contact.id}`, contact) + .pipe((r) => + this.responseStateNotification.handleResponse( + r, + strings.editContactSuccessNotification, + strings.errorNotification + ) + ); } } diff --git a/src/app/strings.ts b/src/app/strings.ts index c7b7fb4..83470ca 100644 --- a/src/app/strings.ts +++ b/src/app/strings.ts @@ -9,11 +9,15 @@ export const strings = Object.freeze({ contactsCompany: "Contact's company", contactsPhone: "Contact's phone", contactList: 'contact list', + createContactSuccessNotification: 'Contact created successfully', + deleteContactSuccessNotification: 'Contact deleted successfully', editContact: 'edit contact', + editContactSuccessNotification: 'Contact edited successfully', editTheContact: 'edit the contact', errorMessageMaxLength: (maxLen: number) => `Must be ${maxLen} characters or fewer.`, errorMessagePhonePattern: `Valid format: + (optional) plus 12 to 15 digits`, errorMessageRequired: 'This field is required.', + errorNotification: 'Oops, there was an error', name: 'name', phone: 'phone', save: 'save',