diff --git a/package-lock.json b/package-lock.json index cfdc5176..b00797bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -427,40 +427,40 @@ } }, "@ngrx/data": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@ngrx/data/-/data-8.0.1.tgz", - "integrity": "sha512-+vXN2czCi8qKNnZvV452TwKYbv5WrC4d5RIR5k5QrkbS8gL6v2hOB9aUo+1jbZ+r4wLxeUtqHzPrBC174PdXJA==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/data/-/data-8.6.0.tgz", + "integrity": "sha512-6rsuqeEJHgfLmN86D5ZDZtyXgZU3OBG39epwiDBgIBZMyIQB8opvKgpmuPvFgAylKUCHRctjTdVNsq8hizI/rg==" }, "@ngrx/effects": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-8.0.1.tgz", - "integrity": "sha512-f15SjzZ670HdHVpu3byOiP5R6MOxh/DMQgP8PynKtmadmkKkM69imGsp+8qDEQNABw5O8oJGAn8aLm76QGPnjQ==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-8.6.0.tgz", + "integrity": "sha512-JdyJLQbv/wnE0ZPY9DcDOtF9PzJuzsKWmIWgIGunHF18wdjk5O8Zpkcrxq18wDRL6geg5UTtNJRMvTQhpDbzow==" }, "@ngrx/entity": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@ngrx/entity/-/entity-8.0.1.tgz", - "integrity": "sha512-mdsrKHxo/uYP+sIjgIWmVbIKrLpqvcUlUamV+QfMPTxQXN9nmFsvNsemcOHN1fCklaGWzLo0tfoF/NVwdhJb5w==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/entity/-/entity-8.6.0.tgz", + "integrity": "sha512-Qq+ANgsHd2/i7gam1j05hxA8kPWQyf5ewtCLlbtMJI/qLmvA6ruSE8PYNNwrt90wm4BWdks2zKE5ERzvPzto0w==" }, "@ngrx/router-store": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-8.0.1.tgz", - "integrity": "sha512-ItgyqjPtjpUmMRKe48pyBMzrntimJwImttxqRa0uMWooPGIJB6zAmbgjn2AtyUqX63IRl8BGT8V3pJ4FLrxIow==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-8.6.0.tgz", + "integrity": "sha512-4Dvl6dfOj15lNZ63wucRNcTEHUi0hEqapOBVRslfAsnaSRo2t1lOvfX7b68IbxPiqzabTBdIeEkJwAC2q/rZZg==" }, "@ngrx/schematics": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@ngrx/schematics/-/schematics-8.0.1.tgz", - "integrity": "sha512-BnHYRHkzAqXKDRQW5WZoSOwxJkHmwsd/bEJ5wRinplVRSUJY0BYgCVe1by1hj37/FAsRH5E6WBtoOgUyn0i/MQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/schematics/-/schematics-8.6.0.tgz", + "integrity": "sha512-d28FVsLWFJYxpMFnqzWvdbFSSPNlLUIezd0c4zlyf4CyNS/C8aw6Lio9xjOJHhgZuHvFuO9GwRrYV/GaS6wC7A==", "dev": true }, "@ngrx/store": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-8.0.1.tgz", - "integrity": "sha512-B6HY8TCFZ4+bUfJAJatF42+F33Qboo7zKc+gxTi6eEioKvNqWkb22K4De5HV2j/l/blXvMOPMSO1Rf/sGqID0w==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-8.6.0.tgz", + "integrity": "sha512-K4cvCEa+5hw9qrETQWO+Cha3YbVCAT8yaIKJr/N35KntTL9mQMjoL+51JWLZfBwPV0e19CFgJIyrBnVUTxwr2A==" }, "@ngrx/store-devtools": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-8.0.1.tgz", - "integrity": "sha512-TxOksSARaQ1hCZM0/qMCun6axbSFuhSEmuW9YLRhuU5m3gvJN4cr5tk960DDVGl8/06YX6Q2CQ7uIgx5qCwbsg==" + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-8.6.0.tgz", + "integrity": "sha512-PWZmiOZE0J56GFfZpuzKLb7w0K2c6OXZSp/eWDeAvtdHFD4/Nas1i4TXtiWWMWWnSZeNs0hNIg4nFJXi2EddJQ==" }, "@ngtools/webpack": { "version": "8.0.1", diff --git a/package.json b/package.json index b8fcb0b3..062140b3 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,12 @@ "@angular/platform-browser": "^8.0.2", "@angular/platform-browser-dynamic": "^8.0.2", "@angular/router": "^8.0.2", - "@ngrx/data": "^8.0.1", - "@ngrx/effects": "^8.0.1", - "@ngrx/entity": "^8.0.1", - "@ngrx/router-store": "^8.0.1", - "@ngrx/store": "^8.0.1", - "@ngrx/store-devtools": "^8.0.1", + "@ngrx/data": "^8.6.0", + "@ngrx/effects": "^8.6.0", + "@ngrx/entity": "^8.6.0", + "@ngrx/router-store": "^8.6.0", + "@ngrx/store": "^8.6.0", + "@ngrx/store-devtools": "^8.6.0", "body-parser": "^1.18.2", "core-js": "^2.4.1", "express": "^4.16.2", @@ -44,7 +44,7 @@ "@angular/cli": "^8.0.1", "@angular/compiler-cli": "^8.0.2", "@angular/language-service": "^8.0.2", - "@ngrx/schematics": "^8.0.1", + "@ngrx/schematics": "^8.6.0", "@types/express": "^4.0.39", "@types/jasmine": "~2.5.53", "@types/jasminewd2": "~2.0.2", diff --git a/src/app/courses/course.actions.ts b/src/app/courses/course.actions.ts index a050871b..4d3d0b1c 100644 --- a/src/app/courses/course.actions.ts +++ b/src/app/courses/course.actions.ts @@ -8,6 +8,32 @@ export const loadAllCourses = createAction( "[Courses Resolver] Load All Courses" ); +export const addCourse = createAction( + "[Edit Course Dialog] Add Course", + props<{ course: Course }>() +); + +export const courseAddedSuccess = createAction( + "[Courses/API] Add Course Success", + props<{ course: Course }>() +); + +export const courseAddedFailure = createAction( + "[Courses/API] Add Course Failure", + props<{ course: Course }>() +); + + +export const courseUpdatedSuccess = createAction( + "[Courses/API] Update Course Success", + props<{ course: Course }>() +); + +export const courseUpdatedFailure = createAction( + "[Courses/API] Update Course Failure", + props<{ course: Partial }>() +); + export const allCoursesLoaded = createAction( "[Load Courses Effect] All Courses Loaded", diff --git a/src/app/courses/courses.effects.ts b/src/app/courses/courses.effects.ts index 1c7e4af1..db1edc12 100644 --- a/src/app/courses/courses.effects.ts +++ b/src/app/courses/courses.effects.ts @@ -2,8 +2,9 @@ import {Injectable} from '@angular/core'; import {Actions, createEffect, ofType} from '@ngrx/effects'; import {CourseActions} from './action-types'; import {CoursesHttpService} from './services/courses-http.service'; -import {concatMap, map} from 'rxjs/operators'; +import {concatMap, map, catchError} from 'rxjs/operators'; import {allCoursesLoaded} from './course.actions'; +import { of } from 'rxjs'; @Injectable() @@ -25,12 +26,31 @@ export class CoursesEffects { () => this.actions$ .pipe( ofType(CourseActions.courseUpdated), - concatMap(action => this.coursesHttpService.saveCourse( - action.update.id, - action.update.changes - )) - ), - {dispatch: false} + concatMap(action => + this.coursesHttpService.saveCourse( + action.update.id, + action.update.changes + ).pipe( + map(course => CourseActions.courseUpdatedSuccess({ course })), + catchError(() => of(CourseActions.courseUpdatedFailure({ course: action.update.changes }))) + ) + ) + ) + ); + + addCourse$ = createEffect( + () => this.actions$ + .pipe( + ofType(CourseActions.addCourse), + concatMap(action => + this.coursesHttpService.addCourse( + action.course + ).pipe( + map(course => CourseActions.courseAddedSuccess({ course })), + catchError(() => of(CourseActions.courseAddedFailure({ course: action.course }))) + ) + ) + ) ); constructor(private actions$: Actions, diff --git a/src/app/courses/courses.selectors.ts b/src/app/courses/courses.selectors.ts index 76861222..4a519a8d 100644 --- a/src/app/courses/courses.selectors.ts +++ b/src/app/courses/courses.selectors.ts @@ -34,3 +34,8 @@ export const areCoursesLoaded = createSelector( selectCoursesState, state => state.allCoursesLoaded ); + +export const selectCourseSaving = createSelector( + selectCoursesState, + state => state.saving +); diff --git a/src/app/courses/edit-course-dialog/edit-course-dialog.component.html b/src/app/courses/edit-course-dialog/edit-course-dialog.component.html index 21351cb5..1012dc9c 100644 --- a/src/app/courses/edit-course-dialog/edit-course-dialog.component.html +++ b/src/app/courses/edit-course-dialog/edit-course-dialog.component.html @@ -5,12 +5,6 @@

{{dialogTitle}}

-
- - - -
- @@ -83,8 +77,8 @@

{{dialogTitle}}

diff --git a/src/app/courses/edit-course-dialog/edit-course-dialog.component.ts b/src/app/courses/edit-course-dialog/edit-course-dialog.component.ts index ab7a9f86..13f35494 100644 --- a/src/app/courses/edit-course-dialog/edit-course-dialog.component.ts +++ b/src/app/courses/edit-course-dialog/edit-course-dialog.component.ts @@ -2,12 +2,14 @@ import {Component, Inject} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; import {Course} from '../model/course'; import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {Observable} from 'rxjs'; +import {Subject, of} from 'rxjs'; import {CoursesHttpService} from '../services/courses-http.service'; import {AppState} from '../../reducers'; import {Store} from '@ngrx/store'; import {Update} from '@ngrx/entity'; -import {courseUpdated} from '../course.actions'; +import {addCourse, courseUpdated} from '../course.actions'; +import {filter, map, mergeMap, tap} from 'rxjs/operators'; +import {selectCourseSaving} from '../courses.selectors'; @Component({ selector: 'course-dialog', @@ -24,7 +26,8 @@ export class EditCourseDialogComponent { mode: 'create' | 'update'; - loading$:Observable; + saves = new Subject(); + saving$ = this.store.select(selectCourseSaving); constructor( private fb: FormBuilder, @@ -54,30 +57,38 @@ export class EditCourseDialogComponent { iconUrl: ['', Validators.required] }); } + + const saved$ = this.saving$.pipe( + filter(saving => !saving) + ); + this.saves.pipe( + map(_ => this.createSaveAction()), + tap(action => this.store.dispatch(action)), + mergeMap(_ => this.mode === 'create' ? saved$ : of(true)) + ) + // no need to unsubscribe here as stream dies with component + .subscribe(_ => { + this.dialogRef.close(); + }); } onClose() { this.dialogRef.close(); } - onSave() { - + private createSaveAction() { const course: Course = { ...this.course, ...this.form.value }; - - const update: Update = { - id: course.id, - changes: course - }; - - this.store.dispatch(courseUpdated({update})); - - this.dialogRef.close(); - - + if (this.mode === 'update') { + const update: Update = { + id: course.id, + changes: course + }; + return courseUpdated({update}); + } else { + return addCourse({ course }); + } } - - } diff --git a/src/app/courses/reducers/course.reducers.ts b/src/app/courses/reducers/course.reducers.ts index dab81d03..5ace72e9 100644 --- a/src/app/courses/reducers/course.reducers.ts +++ b/src/app/courses/reducers/course.reducers.ts @@ -5,7 +5,8 @@ import {CourseActions} from '../action-types'; export interface CoursesState extends EntityState { - allCoursesLoaded: boolean + allCoursesLoaded: boolean; + saving: boolean; } @@ -14,8 +15,9 @@ export const adapter = createEntityAdapter({ }); -export const initialCoursesState = adapter.getInitialState({ - allCoursesLoaded:false +export const initialCoursesState: CoursesState = adapter.getInitialState({ + allCoursesLoaded: false, + saving: false }); @@ -32,7 +34,22 @@ export const coursesReducer = createReducer( on(CourseActions.courseUpdated, (state, action) => - adapter.updateOne(action.update, state) ) + adapter.updateOne( + action.update, + { ...state, saving: true })), + + on(CourseActions.addCourse, (state, _action) => ({ ...state, saving: true })), + on(CourseActions.courseAddedSuccess, (state, action) => + adapter.addOne( + action.course, + { ...state, saving: false })), + + on( + CourseActions.courseUpdatedFailure, + CourseActions.courseUpdatedSuccess, + CourseActions.courseAddedFailure, + (state, _action) => ({ ...state, saving: false } + )) ); diff --git a/src/app/courses/services/courses-http.service.ts b/src/app/courses/services/courses-http.service.ts index 83ddc8b1..cecb3e45 100644 --- a/src/app/courses/services/courses-http.service.ts +++ b/src/app/courses/services/courses-http.service.ts @@ -15,6 +15,10 @@ export class CoursesHttpService { } + addCourse(course: Course) { + return this.http.post('/api/course/', course); + } + findAllCourses(): Observable { return this.http.get('/api/courses') .pipe( @@ -41,8 +45,7 @@ export class CoursesHttpService { saveCourse(courseId: string | number, changes: Partial) { - return this.http.put('/api/course/' + courseId, changes); + return this.http.put('/api/course/' + courseId, changes); } - }