From 32ee7b9325a1f66ca5eae12e9991a47317abfd3d Mon Sep 17 00:00:00 2001 From: Kamil Date: Tue, 16 May 2023 09:18:25 +0200 Subject: [PATCH 1/5] switchMap operator --- .../edit-meeting/edit-meeting.component.ts | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/app/home/people/components/edit-meeting/edit-meeting.component.ts b/src/app/home/people/components/edit-meeting/edit-meeting.component.ts index dbbbaae..3c2eaeb 100644 --- a/src/app/home/people/components/edit-meeting/edit-meeting.component.ts +++ b/src/app/home/people/components/edit-meeting/edit-meeting.component.ts @@ -3,6 +3,10 @@ import { AttritionRisk, MeetingNotes } from '../../models/meeting-notes.model'; import { FormBuilder, FormGroup } from '@angular/forms'; import { YesNoToBooleanMapper } from 'src/app/shared/mappers/yes-no.mapper'; import { Location } from '@angular/common'; +import { debounceTime, switchMap } from 'rxjs'; +import { UnitsService } from '../../services/units.service'; +import { Store } from '@ngrx/store'; +import { selectUserId } from 'src/app/login/store/login.selectors'; @Component({ selector: 'app-edit-meeting', @@ -12,6 +16,7 @@ import { Location } from '@angular/common'; export class EditMeetingComponent implements OnInit { meetingData!: MeetingNotes; + notesId?: string; meetingForm!: FormGroup; cdkAutosizeMinRows = 1; cdkAutosizeMaxRows = 4; @@ -25,10 +30,13 @@ export class EditMeetingComponent implements OnInit { constructor( private formBuilder: FormBuilder, private yesNoMapper: YesNoToBooleanMapper, - private location: Location) { } + private location: Location, + private store: Store, + private unitsService: UnitsService) { } ngOnInit(): void { this.meetingData = history.state; + this.notesId = this.meetingData.notesId; this.meetingForm = this.formBuilder.group({ comments: [this.meetingData.comments], questions: [this.meetingData.questions], @@ -42,6 +50,42 @@ export class EditMeetingComponent implements OnInit { attritionRisk: [this.meetingData.attritionRisk], oneToOneReportSent: [this.yesNoMapper.toModel(this.meetingData.oneToOneReportSent)] }); + + let userId: string | undefined; + this.store.select(selectUserId) + .subscribe(value => userId = value); + + if (!userId) { + throw new Error('userId not found in Store'); + } + + // Below subscription to valueChanges event was implemented to exercise switchMap operator + // Behavior in UI: when user edits Comments form field, request is sent to fetch data from BE + // and update Questions form field if no other Observable is emitted from valueChanges for 2 seconds + this.comments.valueChanges + .pipe( + debounceTime(2000), + switchMap(() => this.unitsService.getUnits(userId!)) + ) + .subscribe(units => { + const meetingData = units + .flatMap(unit => unit.people) + .flatMap(person => person.meetings) + .find(meeting => meeting.notesId === this.notesId)!; + + setTimeout(() => { + this.questions.setValue(meetingData.questions); + }) + } + ); + } + + get comments() { + return this.meetingForm.get('comments')!; + } + + get questions() { + return this.meetingForm.get('questions')!; } onSubmit() { From edf1cbde1004001ef0ec3ef8a3fee2782dab65e3 Mon Sep 17 00:00:00 2001 From: Kamil Date: Tue, 16 May 2023 16:21:58 +0200 Subject: [PATCH 2/5] ReplaySubject usage --- src/app/home/home.module.ts | 5 +-- .../edit-meeting/edit-meeting.component.ts | 2 +- .../components/people/people.component.ts | 3 +- src/app/home/people/people.module.ts | 3 +- .../services/units.service.spec.ts | 0 .../{people => }/services/units.service.ts | 17 ++++++++-- .../components/summary/summary.component.html | 13 +++++++- .../components/summary/summary.component.ts | 31 +++++++++++++++++-- src/app/home/summary/summary.module.ts | 4 ++- 9 files changed, 65 insertions(+), 13 deletions(-) rename src/app/home/{people => }/services/units.service.spec.ts (100%) rename src/app/home/{people => }/services/units.service.ts (51%) diff --git a/src/app/home/home.module.ts b/src/app/home/home.module.ts index 8e22c7e..e5f2f05 100644 --- a/src/app/home/home.module.ts +++ b/src/app/home/home.module.ts @@ -1,8 +1,9 @@ -import { CommonModule } from "@angular/common"; +import { CommonModule, DatePipe } from "@angular/common"; import { NgModule } from "@angular/core"; import { CoreModule } from "../core/core.module"; import { HomeComponent } from "./components/home/home.component"; import { HomeRoutingModule } from "./home-routing.module"; +import { UnitsService } from "./services/units.service"; @NgModule({ declarations: [HomeComponent], @@ -11,6 +12,6 @@ import { HomeRoutingModule } from "./home-routing.module"; HomeRoutingModule, CoreModule ], - providers: [], + providers: [UnitsService, DatePipe], }) export class HomeModule { } \ No newline at end of file diff --git a/src/app/home/people/components/edit-meeting/edit-meeting.component.ts b/src/app/home/people/components/edit-meeting/edit-meeting.component.ts index 3c2eaeb..aa9eb5a 100644 --- a/src/app/home/people/components/edit-meeting/edit-meeting.component.ts +++ b/src/app/home/people/components/edit-meeting/edit-meeting.component.ts @@ -4,9 +4,9 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { YesNoToBooleanMapper } from 'src/app/shared/mappers/yes-no.mapper'; import { Location } from '@angular/common'; import { debounceTime, switchMap } from 'rxjs'; -import { UnitsService } from '../../services/units.service'; import { Store } from '@ngrx/store'; import { selectUserId } from 'src/app/login/store/login.selectors'; +import { UnitsService } from 'src/app/home/services/units.service'; @Component({ selector: 'app-edit-meeting', diff --git a/src/app/home/people/components/people/people.component.ts b/src/app/home/people/components/people/people.component.ts index e9b6f45..44023be 100644 --- a/src/app/home/people/components/people/people.component.ts +++ b/src/app/home/people/components/people/people.component.ts @@ -2,9 +2,10 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Observable, Subscription, of } from 'rxjs'; import { Unit } from 'src/app/home/people/models/unit.model'; import { ModalService } from '../../services/modal.service'; -import { UnitsService } from '../../services/units.service'; + import { Store } from '@ngrx/store'; import { selectUserId } from 'src/app/login/store/login.selectors'; +import { UnitsService } from 'src/app/home/services/units.service'; @Component({ selector: 'app-people', diff --git a/src/app/home/people/people.module.ts b/src/app/home/people/people.module.ts index be7ca8d..7d3d167 100644 --- a/src/app/home/people/people.module.ts +++ b/src/app/home/people/people.module.ts @@ -3,7 +3,6 @@ import { NgModule } from "@angular/core"; import { PeopleComponent } from "./components/people/people.component"; import { UnitComponent } from "./components/unit/unit.component"; import { PeopleRoutingModule } from "./people-routing.module"; -import { UnitsService } from "./services/units.service"; import { PersonComponent } from './components/person/person.component'; import { MeetingComponent } from './components/meeting/meeting.component'; import { NgMaterialModule } from "src/app/ng-material/ng-material.module"; @@ -32,6 +31,6 @@ import { EditMeetingComponent } from './components/edit-meeting/edit-meeting.com ReactiveFormsModule, SharedModule ], - providers: [UnitsService], + providers: [], }) export class PeopleModule { } \ No newline at end of file diff --git a/src/app/home/people/services/units.service.spec.ts b/src/app/home/services/units.service.spec.ts similarity index 100% rename from src/app/home/people/services/units.service.spec.ts rename to src/app/home/services/units.service.spec.ts diff --git a/src/app/home/people/services/units.service.ts b/src/app/home/services/units.service.ts similarity index 51% rename from src/app/home/people/services/units.service.ts rename to src/app/home/services/units.service.ts index 867307d..e32d7a5 100644 --- a/src/app/home/people/services/units.service.ts +++ b/src/app/home/services/units.service.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; +import { Observable, ReplaySubject, of, switchMap } from 'rxjs'; import { ConfigService } from 'src/app/core/services/config.service'; import { Unit } from 'src/app/home/people/models/unit.model'; @@ -8,6 +8,7 @@ import { Unit } from 'src/app/home/people/models/unit.model'; export class UnitsService { private readonly apiBaseUrl; + private history$ = new ReplaySubject(); constructor( private http: HttpClient, @@ -17,6 +18,18 @@ export class UnitsService { } getUnits(userId: string): Observable { - return this.http.get(`${this.apiBaseUrl}/${userId}/units`); + return this.http.get(`${this.apiBaseUrl}/${userId}/units`).pipe( + switchMap(units => { + this.history$.next(units); + return of(units); + }) + ); + } + + // Method uses ReplaySubject which emits 'historical' values for new subscribers + // SummaryComponent uses it to display history of meeting entries count which is updated + // after every request sent to /units endpoint + getHistoricalData(subscription: any) { + return this.history$.asObservable().subscribe(subscription); } } diff --git a/src/app/home/summary/components/summary/summary.component.html b/src/app/home/summary/components/summary/summary.component.html index eb0512c..886dca5 100644 --- a/src/app/home/summary/components/summary/summary.component.html +++ b/src/app/home/summary/components/summary/summary.component.html @@ -1 +1,12 @@ -

summary works!

+ + + + + + + + + + + +
Number of meetings
{{ entry.meetings.length }}
diff --git a/src/app/home/summary/components/summary/summary.component.ts b/src/app/home/summary/components/summary/summary.component.ts index 011993b..f7f6b65 100644 --- a/src/app/home/summary/components/summary/summary.component.ts +++ b/src/app/home/summary/components/summary/summary.component.ts @@ -1,15 +1,40 @@ -import { Component, OnInit } from '@angular/core'; +import { DatePipe } from '@angular/common'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { MeetingNotes } from 'src/app/home/people/models/meeting-notes.model'; +import { Unit } from 'src/app/home/people/models/unit.model'; +import { UnitsService } from 'src/app/home/services/units.service'; @Component({ selector: 'app-summary', templateUrl: './summary.component.html', styleUrls: ['./summary.component.css'] }) -export class SummaryComponent implements OnInit { +export class SummaryComponent implements OnInit, OnDestroy { - constructor() { } + units$?: Subscription; + meetings: MeetingNotes[] = []; + history: MeetingHistory[] = []; + + constructor(private readonly unitsService: UnitsService, private datePipe: DatePipe) { } + + ngOnDestroy(): void { + this.units$?.unsubscribe(); + } ngOnInit(): void { + this.units$ = this.unitsService.getHistoricalData( + (units: Unit[]) => { + this.history.push({ + meetings: units + .flatMap(unit => unit.people) + .flatMap(person => person.meetings) + }); + } + ); } +} +export interface MeetingHistory { + meetings: MeetingNotes[]; } diff --git a/src/app/home/summary/summary.module.ts b/src/app/home/summary/summary.module.ts index a8a3a3a..6cfd2b5 100644 --- a/src/app/home/summary/summary.module.ts +++ b/src/app/home/summary/summary.module.ts @@ -2,12 +2,14 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { SummaryComponent } from "./components/summary/summary.component"; import { SummaryRoutingModule } from "./summary-routing.module"; +import { NgMaterialModule } from "src/app/ng-material/ng-material.module"; @NgModule({ declarations: [SummaryComponent], imports: [ CommonModule, - SummaryRoutingModule + SummaryRoutingModule, + NgMaterialModule ], providers: [], }) From 659c97fe35f334c3eddebb7c1c194ee58ce75a14 Mon Sep 17 00:00:00 2001 From: Kamil Date: Tue, 16 May 2023 20:47:58 +0200 Subject: [PATCH 3/5] Async validator --- .../add-meeting-modal.component.html | 8 ++++++++ .../add-meeting-modal.component.ts | 7 ++++++- src/app/shared/validators/field.validator.ts | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 src/app/shared/validators/field.validator.ts diff --git a/src/app/home/people/components/add-meeting-modal/add-meeting-modal.component.html b/src/app/home/people/components/add-meeting-modal/add-meeting-modal.component.html index ee8e538..6fc7f5b 100644 --- a/src/app/home/people/components/add-meeting-modal/add-meeting-modal.component.html +++ b/src/app/home/people/components/add-meeting-modal/add-meeting-modal.component.html @@ -13,6 +13,10 @@

Add Meeting Notes for {{ personData.name }}

formControlName="comments"> +
+ +
+ Questions