From 1466dc741cceeabc0d7bb72d340336b60332c82c Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 24 Apr 2020 12:30:38 +0530 Subject: [PATCH 1/2] adds basic example with tradition code --- package-lock.json | 31 +++++---- package.json | 2 +- server/get-courses.route.ts | 10 +-- server/login.route.ts | 2 +- server/save-course.route.ts | 4 +- server/search-lessons.route.ts | 2 +- src/app/app.component.html | 8 +-- src/app/app.component.ts | 6 +- src/app/app.module.ts | 8 +-- .../course-dialog.component.html | 3 - .../course-dialog/course-dialog.component.ts | 29 +++------ src/app/course/course.component.html | 39 +++++------ src/app/course/course.component.ts | 42 ++---------- src/app/home/home.component.css | 10 +++ src/app/home/home.component.html | 65 ++++++++++++++++--- src/app/home/home.component.ts | 50 ++++++++++---- src/app/lesson/lesson.component.html | 16 +---- src/app/lesson/lesson.component.ts | 13 +--- src/app/loading/loading.component.css | 22 +++---- src/app/loading/loading.component.html | 8 --- src/app/loading/loading.component.ts | 3 +- src/app/login/login.component.ts | 14 +--- src/app/messages/messages.component.html | 16 ----- src/app/messages/messages.component.ts | 16 +---- src/app/model/course.ts | 1 - .../search-lessons.component.css | 7 +- .../search-lessons.component.html | 48 ++++---------- .../search-lessons.component.ts | 24 +------ 28 files changed, 204 insertions(+), 295 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b1553b..044e155 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "reactive-angular-application", + "name": "rxjs-course", "version": "0.0.0", "lockfileVersion": 1, "requires": true, @@ -265,11 +265,12 @@ "integrity": "sha512-jB8+SC3vMztW5zt5UYVmtVwqIWE33UyEjbP5JPba3I3bLRK5E059LcJmN1rSdJHItgIAdG9Y1I0WJ6aiSFyp4Q==" }, "@angular/cdk": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.0.0.tgz", - "integrity": "sha512-2kYpyYbewIB6fubSIDMvSprJLNplRZoL/AtXW3od4dLyRxtzX+7iWTAtzUG/dhq8CKev0lpd1HENh5lLR/Lhjw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-8.0.0.tgz", + "integrity": "sha512-2vsRWEHNARe0iRmqgzvM67gwfRy+aKvdef4Qu9L+ndSsTrrZT3tSgG8SMn1v9SfBHnx5G8mo4d1AMquXG69AuQ==", "requires": { - "parse5": "^5.0.0" + "parse5": "^5.0.0", + "tslib": "^1.7.1" } }, "@angular/cli": { @@ -569,9 +570,12 @@ "dev": true }, "@angular/material": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.0.0.tgz", - "integrity": "sha512-QxN2rmR5mvg2YE1NoIGWLpbnmcJq0iFidzy6odzvN17+XkoCJBZ65IdYsHrJgfwGpoIy6bywuixrDHHcSh9I5w==" + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-8.0.0.tgz", + "integrity": "sha512-c7O7GhZd46xF2WB6T/YPam5lJkTgQLdIS53IqwZIFhL427+SEfPvejVzRnVfZCI3NdrKiWt/5VsvtQZwWzlGvw==", + "requires": { + "tslib": "^1.7.1" + } }, "@angular/material-moment-adapter": { "version": "9.0.0", @@ -5942,6 +5946,11 @@ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", "dev": true }, + "hammerjs": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", + "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" + }, "handle-thing": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", @@ -8771,9 +8780,9 @@ "dev": true }, "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", "optional": true }, "parseqs": { diff --git a/package.json b/package.json index 01732b6..f754ed7 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "reactive-angular-application", + "name": "reactive-angular-course", "version": "0.0.0", "license": "MIT", "scripts": { diff --git a/server/get-courses.route.ts b/server/get-courses.route.ts index cbfff14..2e66ca1 100644 --- a/server/get-courses.route.ts +++ b/server/get-courses.route.ts @@ -9,19 +9,15 @@ export function getAllCourses(req: Request, res: Response) { /* console.log("ERROR loading courses!"); - res.status(500).json({message: 'error occurred.'}); + res.status(500).json({message: 'random error occurred.'}); return; -*/ - - + */ setTimeout(() => { res.status(200).json({payload:Object.values(COURSES)}); - }, 1500); - - + }, 200); } diff --git a/server/login.route.ts b/server/login.route.ts index 1cd6c4e..cf3a360 100644 --- a/server/login.route.ts +++ b/server/login.route.ts @@ -13,7 +13,7 @@ export function loginUser(req: Request, res: Response) { const user = authenticate(email, password); if (user) { - res.status(200).json({email: user.email}); + res.status(200).json({email: user.email}); } else { res.sendStatus(403); diff --git a/server/save-course.route.ts b/server/save-course.route.ts index e29356c..5977d33 100644 --- a/server/save-course.route.ts +++ b/server/save-course.route.ts @@ -5,12 +5,12 @@ import {setTimeout} from 'timers'; export function saveCourse(req: Request, res: Response) { -/* + /* console.log("ERROR saving course!"); res.sendStatus(500); return; -*/ + */ const id = req.params["id"], changes = req.body; diff --git a/server/search-lessons.route.ts b/server/search-lessons.route.ts index 9e71fb1..008ab1f 100644 --- a/server/search-lessons.route.ts +++ b/server/search-lessons.route.ts @@ -20,7 +20,7 @@ export function searchLessons(req: Request, res: Response) { let lessons; if (courseId) { - lessons = Object.values(LESSONS).filter(lesson => lesson.courseId == courseId).sort((l1, l2) => l1.id - l2.id); + lessons = Object.values(LESSONS).filter(lesson => lesson.courseId == courseId).sort((l1, l2) => l1.id - l2.id); } else { lessons = Object.values(LESSONS); diff --git a/src/app/app.component.html b/src/app/app.component.html index 1f3294d..a7482dd 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -22,12 +22,12 @@ Register - + account_circle Login - + exit_to_app Logout @@ -51,10 +51,6 @@ - - - - diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 29fde6f..c94ed83 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,7 +1,4 @@ import {Component, OnInit} from '@angular/core'; -import {LoadingService} from './loading/loading.service'; -import {MessagesService} from './messages/messages.service'; -import {AuthStore} from './services/auth.store'; @@ -12,7 +9,7 @@ import {AuthStore} from './services/auth.store'; }) export class AppComponent implements OnInit { - constructor(public auth: AuthStore) { + constructor() { } @@ -22,7 +19,6 @@ export class AppComponent implements OnInit { } logout() { - this.auth.logout(); } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 82d1499..badc903 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -33,9 +33,6 @@ import {SafeUrlPipe} from './common/safe-url.pipe'; import {MessagesComponent} from './messages/messages.component'; import {SearchLessonsComponent} from './search-lessons/search-lessons.component'; import { LoadingComponent } from './loading/loading.component'; -import { CoursesCardListComponent } from './courses-card-list/courses-card-list.component'; -import {LoadingService} from './loading/loading.service'; -import {MessagesService} from './messages/messages.service'; @NgModule({ declarations: [ @@ -49,8 +46,7 @@ import {MessagesService} from './messages/messages.service'; SafeUrlPipe, MessagesComponent, SearchLessonsComponent, - LoadingComponent, - CoursesCardListComponent + LoadingComponent ], imports: [ @@ -78,8 +74,6 @@ import {MessagesService} from './messages/messages.service'; ReactiveFormsModule ], providers: [ - LoadingService, - MessagesService ], bootstrap: [AppComponent], entryComponents: [CourseDialogComponent] diff --git a/src/app/course-dialog/course-dialog.component.html b/src/app/course-dialog/course-dialog.component.html index b6b897f..910bc4c 100644 --- a/src/app/course-dialog/course-dialog.component.html +++ b/src/app/course-dialog/course-dialog.component.html @@ -1,8 +1,5 @@

{{course.description}}

- - - diff --git a/src/app/course-dialog/course-dialog.component.ts b/src/app/course-dialog/course-dialog.component.ts index d5f2863..6bcaa85 100644 --- a/src/app/course-dialog/course-dialog.component.ts +++ b/src/app/course-dialog/course-dialog.component.ts @@ -1,25 +1,17 @@ -import {AfterViewInit, Component, Inject} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, Inject, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; import {Course} from "../model/course"; import {FormBuilder, Validators, FormGroup} from "@angular/forms"; import * as moment from 'moment'; -import {CoursesService} from '../services/courses.service'; -import {LoadingService} from '../loading/loading.service'; -import {MessagesService} from '../messages/messages.service'; -import {throwError} from 'rxjs'; import {catchError} from 'rxjs/operators'; -import {CoursesStore} from '../services/courses.store'; +import {throwError} from 'rxjs'; @Component({ selector: 'course-dialog', templateUrl: './course-dialog.component.html', - styleUrls: ['./course-dialog.component.css'], - providers: [ - LoadingService, - MessagesService - ] + styleUrls: ['./course-dialog.component.css'] }) -export class CourseDialogComponent { +export class CourseDialogComponent implements AfterViewInit { form: FormGroup; @@ -28,9 +20,7 @@ export class CourseDialogComponent { constructor( private fb: FormBuilder, private dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) course:Course, - private coursesStore: CoursesStore, - private messagesService: MessagesService) { + @Inject(MAT_DIALOG_DATA) course:Course) { this.course = course; @@ -43,14 +33,13 @@ export class CourseDialogComponent { } - save() { + ngAfterViewInit() { - const changes = this.form.value; + } - this.coursesStore.saveCourse(this.course.id, changes) - .subscribe(); + save() { - this.dialogRef.close(changes); + const changes = this.form.value; } diff --git a/src/app/course/course.component.html b/src/app/course/course.component.html index d5b819c..2da206c 100644 --- a/src/app/course/course.component.html +++ b/src/app/course/course.component.html @@ -1,34 +1,27 @@ - -
+
-

{{data.course?.description}}

+

{{course?.description}}

- + - +
- + + + + + - - - - - - - - - - - - -
#DescriptionDuration
#DescriptionDuration
{{lesson.seqNo}}{{lesson.description}}{{lesson.duration}}
- -
- - + + {{lesson.seqNo}} + {{lesson.description}} + {{lesson.duration}} + + +
diff --git a/src/app/course/course.component.ts b/src/app/course/course.component.ts index 2169f72..ba3519e 100644 --- a/src/app/course/course.component.ts +++ b/src/app/course/course.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {Course} from '../model/course'; import { @@ -13,58 +13,28 @@ import { withLatestFrom, concatAll, shareReplay, catchError } from 'rxjs/operators'; -import {merge, fromEvent, Observable, concat, throwError, combineLatest} from 'rxjs'; +import {merge, fromEvent, Observable, concat, throwError} from 'rxjs'; import {Lesson} from '../model/lesson'; -import {CoursesService} from '../services/courses.service'; - - -interface CourseData { - course: Course; - lessons: Lesson[]; -} @Component({ selector: 'course', templateUrl: './course.component.html', - styleUrls: ['./course.component.css'], - changeDetection: ChangeDetectionStrategy.OnPush + styleUrls: ['./course.component.css'] }) export class CourseComponent implements OnInit { - data$: Observable; + course: Course; + lessons: Lesson[]; - constructor(private route: ActivatedRoute, - private coursesService: CoursesService) { + constructor(private route: ActivatedRoute) { } ngOnInit() { - const courseId = parseInt(this.route.snapshot.paramMap.get("courseId")); - - const course$ = this.coursesService.loadCourseById(courseId) - .pipe( - startWith(null) - ); - - const lessons$ = this.coursesService.loadAllCourseLessons(courseId) - .pipe( - startWith([]) - ); - - this.data$ = combineLatest([course$, lessons$]) - .pipe( - map(([course, lessons]) => { - return { - course, - lessons - } - }), - tap(console.log) - ); } diff --git a/src/app/home/home.component.css b/src/app/home/home.component.css index aa8317d..0e5a130 100644 --- a/src/app/home/home.component.css +++ b/src/app/home/home.component.css @@ -6,3 +6,13 @@ } + + +.course-card { + margin: 20px 10px; +} + +.course-actions { + text-align: center; +} + diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index e4a1f77..c84039e 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -1,24 +1,73 @@
-

All Courses

+

All Courses

- + - + - + - + - + {{course.description}} + + + + + + +

{{course.longDescription}}

+
+ + + + + + + + + + + +
- + + + + + {{course.description}} + + + + + + +

{{course.longDescription}}

+
+ + + + + + -
+ +
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 903cd0f..f66828d 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -1,36 +1,60 @@ -import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; -import {Course} from '../model/course'; -import {Observable} from 'rxjs'; -import {CoursesStore} from '../services/courses.store'; +import {Component, OnInit} from '@angular/core'; +import {Course, sortCoursesBySeqNo} from '../model/course'; +import {interval, noop, Observable, of, throwError, timer} from 'rxjs'; +import {catchError, delay, delayWhen, filter, finalize, map, retryWhen, shareReplay, tap} from 'rxjs/operators'; +import {HttpClient} from '@angular/common/http'; +import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; +import {CourseDialogComponent} from '../course-dialog/course-dialog.component'; @Component({ selector: 'home', templateUrl: './home.component.html', - styleUrls: ['./home.component.css'], - changeDetection: ChangeDetectionStrategy.OnPush + styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { - beginnerCourses$: Observable; + beginnerCourses: Course[]; - advancedCourses$: Observable; + advancedCourses: Course[]; - constructor(private coursesStore: CoursesStore) { + + constructor(private http: HttpClient, private dialog: MatDialog) { } ngOnInit() { - this.reloadCourses(); + + this.http.get('/api/courses') + .subscribe( + res => { + + const courses: Course[] = res["payload"].sort(sortCoursesBySeqNo); + + this.beginnerCourses = courses.filter(course => course.category == "BEGINNER"); + + this.advancedCourses = courses.filter(course => course.category == "ADVANCED"); + + }); + } - reloadCourses() { + editCourse(course: Course) { + + const dialogConfig = new MatDialogConfig(); + + dialogConfig.disableClose = true; + dialogConfig.autoFocus = true; + dialogConfig.width = "400px"; - this.beginnerCourses$ = this.coursesStore.filterByCategory("BEGINNER"); + dialogConfig.data = course; + + const dialogRef = this.dialog.open(CourseDialogComponent, dialogConfig); - this.advancedCourses$ = this.coursesStore.filterByCategory("ADVANCED"); } } + + diff --git a/src/app/lesson/lesson.component.html b/src/app/lesson/lesson.component.html index 2175241..131b285 100644 --- a/src/app/lesson/lesson.component.html +++ b/src/app/lesson/lesson.component.html @@ -1,15 +1 @@ - -
- -

{{lesson.description}}

- -
Duration: {{lesson.duration}}
- - - - -
+

lesson works!

diff --git a/src/app/lesson/lesson.component.ts b/src/app/lesson/lesson.component.ts index 5df54cd..2f4a142 100644 --- a/src/app/lesson/lesson.component.ts +++ b/src/app/lesson/lesson.component.ts @@ -1,20 +1,11 @@ -import {Component, Input, OnInit} from '@angular/core'; -import {Lesson} from '../model/lesson'; +import { Component, OnInit } from '@angular/core'; @Component({ selector: 'lesson', templateUrl: './lesson.component.html', styleUrls: ['./lesson.component.css'] }) -export class LessonComponent implements OnInit { +export class LessonComponent { - @Input() - lesson:Lesson; - - constructor() { } - - ngOnInit() { - - } } diff --git a/src/app/loading/loading.component.css b/src/app/loading/loading.component.css index bb7e4ee..96e82f0 100644 --- a/src/app/loading/loading.component.css +++ b/src/app/loading/loading.component.css @@ -1,15 +1,13 @@ .spinner-container { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - display: flex; - justify-content: center; - align-items: center; - background: rgba(0, 0, 0, 0.32); - z-index: 20000; + position: fixed; + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + background:rgba(0, 0, 0, 0.32); + z-index: 2000; } - - diff --git a/src/app/loading/loading.component.html b/src/app/loading/loading.component.html index f7516e3..8b13789 100644 --- a/src/app/loading/loading.component.html +++ b/src/app/loading/loading.component.html @@ -1,9 +1 @@ - -
- - - -
- - diff --git a/src/app/loading/loading.component.ts b/src/app/loading/loading.component.ts index f9ca4f0..5ee6516 100644 --- a/src/app/loading/loading.component.ts +++ b/src/app/loading/loading.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core'; import {Observable} from 'rxjs'; -import {LoadingService} from './loading.service'; @Component({ selector: 'loading', @@ -10,7 +9,7 @@ import {LoadingService} from './loading.service'; export class LoadingComponent implements OnInit { - constructor(public loadingService: LoadingService) { + constructor() { } diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index fa6bcb5..343488e 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -3,7 +3,6 @@ import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import {Router} from '@angular/router'; -import {AuthStore} from '../services/auth.store'; @Component({ selector: 'login', @@ -16,8 +15,7 @@ export class LoginComponent implements OnInit { constructor( private fb: FormBuilder, - private router: Router, - private auth: AuthStore) { + private router: Router) { this.form = fb.group({ email: ['test@angular-university.io', [Validators.required]], @@ -34,16 +32,6 @@ export class LoginComponent implements OnInit { const val = this.form.value; - this.auth.login(val.email, val.password) - .subscribe( - () => { - this.router.navigateByUrl("/courses") - }, - err => { - alert("Login failed!"); - } - ); - } diff --git a/src/app/messages/messages.component.html b/src/app/messages/messages.component.html index bc0e54c..e69de29 100644 --- a/src/app/messages/messages.component.html +++ b/src/app/messages/messages.component.html @@ -1,16 +0,0 @@ - - - -
- -
- {{error}} -
- - close - -
- -
- - diff --git a/src/app/messages/messages.component.ts b/src/app/messages/messages.component.ts index 609a1a3..a85db85 100644 --- a/src/app/messages/messages.component.ts +++ b/src/app/messages/messages.component.ts @@ -2,7 +2,6 @@ import { Component, OnInit } from '@angular/core'; import {Observable} from 'rxjs'; import {Message} from '../model/message'; import {tap} from 'rxjs/operators'; -import {MessagesService} from './messages.service'; @Component({ selector: 'messages', @@ -11,28 +10,19 @@ import {MessagesService} from './messages.service'; }) export class MessagesComponent implements OnInit { - showMessages = false; - errors$: Observable; - - - constructor(public messagesService: MessagesService) { - - console.log("Created messages component"); + constructor() { } ngOnInit() { - this.errors$ = this.messagesService.errors$ - .pipe( - tap(() => this.showMessages = true) - ); + } onClose() { - this.showMessages = false; + } diff --git a/src/app/model/course.ts b/src/app/model/course.ts index 0335627..9870ddd 100644 --- a/src/app/model/course.ts +++ b/src/app/model/course.ts @@ -1,4 +1,3 @@ - export interface Course { id: string; description: string; diff --git a/src/app/search-lessons/search-lessons.component.css b/src/app/search-lessons/search-lessons.component.css index ff7d70e..960fc25 100644 --- a/src/app/search-lessons/search-lessons.component.css +++ b/src/app/search-lessons/search-lessons.component.css @@ -26,16 +26,15 @@ font-size: 20px; } -.back-btn { - margin-top: 30px; -} .lessons-table { min-height: 360px; margin-top: 10px; } - +.back-btn { + margin-top: 30px; +} .search-bar { margin-top: 20px; diff --git a/src/app/search-lessons/search-lessons.component.html b/src/app/search-lessons/search-lessons.component.html index 53c4f8d..d746a25 100644 --- a/src/app/search-lessons/search-lessons.component.html +++ b/src/app/search-lessons/search-lessons.component.html @@ -8,48 +8,26 @@

Search All Lessons

- - + - + + + + + -
#DescriptionDuration
- - - - - - - - - - - - - -
#DescriptionDuration
{{lesson.seqNo}}{{lesson.description}}{{lesson.duration}}
- -
- - - - - - - - - - + + diff --git a/src/app/search-lessons/search-lessons.component.ts b/src/app/search-lessons/search-lessons.component.ts index 52f5314..fd85ccd 100644 --- a/src/app/search-lessons/search-lessons.component.ts +++ b/src/app/search-lessons/search-lessons.component.ts @@ -1,4 +1,4 @@ -import {AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {Course} from '../model/course'; import { @@ -15,22 +15,16 @@ import { } from 'rxjs/operators'; import {merge, fromEvent, Observable, concat} from 'rxjs'; import {Lesson} from '../model/lesson'; -import {CoursesService} from '../services/courses.service'; @Component({ selector: 'course', templateUrl: './search-lessons.component.html', - styleUrls: ['./search-lessons.component.css'], - changeDetection: ChangeDetectionStrategy.OnPush + styleUrls: ['./search-lessons.component.css'] }) export class SearchLessonsComponent implements OnInit { - searchResults$ : Observable; - - activeLesson:Lesson; - - constructor(private coursesService: CoursesService) { + constructor() { } @@ -40,18 +34,6 @@ export class SearchLessonsComponent implements OnInit { } - onSearch(search:string) { - this.searchResults$ = this.coursesService.searchLessons(search); - } - - openLesson(lesson:Lesson) { - this.activeLesson = lesson; - } - - onBackToSearch() { - this.activeLesson = null; - } - } From 31b251e00dc789083c11aab6a27387b0b2211be9 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 24 Apr 2020 19:43:52 +0530 Subject: [PATCH 2/2] Creates state less Observable service, make a reactive component completed display course list and update course section --- src/app/app.module.ts | 4 +- .../cours-list.component.css} | 13 ++- src/app/cours-list/cours-list.component.html | 28 ++++++ .../cours-list.component.spec.ts} | 12 +-- src/app/cours-list/cours-list.component.ts | 46 ++++++++++ .../course-dialog/course-dialog.component.ts | 9 +- .../courses-card-list.component.html | 29 ------- .../courses-card-list.component.ts | 52 ----------- src/app/home/home.component.html | 67 +-------------- src/app/home/home.component.ts | 45 +++++----- src/app/loading/loading.service.ts | 35 -------- src/app/messages/messages.service.ts | 20 ----- src/app/services/auth.store.ts | 52 ----------- src/app/services/courses.service.ts | 76 ---------------- src/app/services/courses.store.ts | 86 ------------------- src/app/shared/course.service.ts | 36 ++++++++ 16 files changed, 159 insertions(+), 451 deletions(-) rename src/app/{courses-card-list/courses-card-list.component.css => cours-list/cours-list.component.css} (63%) create mode 100644 src/app/cours-list/cours-list.component.html rename src/app/{courses-card-list/courses-card-list.component.spec.ts => cours-list/cours-list.component.spec.ts} (51%) create mode 100644 src/app/cours-list/cours-list.component.ts delete mode 100644 src/app/courses-card-list/courses-card-list.component.html delete mode 100644 src/app/courses-card-list/courses-card-list.component.ts delete mode 100644 src/app/loading/loading.service.ts delete mode 100644 src/app/messages/messages.service.ts delete mode 100644 src/app/services/auth.store.ts delete mode 100644 src/app/services/courses.service.ts delete mode 100644 src/app/services/courses.store.ts create mode 100644 src/app/shared/course.service.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index badc903..7e9dfde 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -33,6 +33,7 @@ import {SafeUrlPipe} from './common/safe-url.pipe'; import {MessagesComponent} from './messages/messages.component'; import {SearchLessonsComponent} from './search-lessons/search-lessons.component'; import { LoadingComponent } from './loading/loading.component'; +import { CoursListComponent } from './cours-list/cours-list.component'; @NgModule({ declarations: [ @@ -46,7 +47,8 @@ import { LoadingComponent } from './loading/loading.component'; SafeUrlPipe, MessagesComponent, SearchLessonsComponent, - LoadingComponent + LoadingComponent, + CoursListComponent ], imports: [ diff --git a/src/app/courses-card-list/courses-card-list.component.css b/src/app/cours-list/cours-list.component.css similarity index 63% rename from src/app/courses-card-list/courses-card-list.component.css rename to src/app/cours-list/cours-list.component.css index 2de1bc5..605a9fd 100644 --- a/src/app/courses-card-list/courses-card-list.component.css +++ b/src/app/cours-list/cours-list.component.css @@ -1,10 +1,9 @@ - - .course-card { margin: 20px 10px; -} - -.course-actions { + } + + .course-actions { text-align: center; -} - + } + + \ No newline at end of file diff --git a/src/app/cours-list/cours-list.component.html b/src/app/cours-list/cours-list.component.html new file mode 100644 index 0000000..a2e2e44 --- /dev/null +++ b/src/app/cours-list/cours-list.component.html @@ -0,0 +1,28 @@ + + + + + {{course.description}} + + + + + + +

{{course.longDescription}}

+
+ + + + + + + + + +
\ No newline at end of file diff --git a/src/app/courses-card-list/courses-card-list.component.spec.ts b/src/app/cours-list/cours-list.component.spec.ts similarity index 51% rename from src/app/courses-card-list/courses-card-list.component.spec.ts rename to src/app/cours-list/cours-list.component.spec.ts index e37fbad..5b31ac7 100644 --- a/src/app/courses-card-list/courses-card-list.component.spec.ts +++ b/src/app/cours-list/cours-list.component.spec.ts @@ -1,20 +1,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CoursesCardListComponent } from './courses-card-list.component'; +import { CoursListComponent } from './cours-list.component'; -describe('CoursesCardListComponent', () => { - let component: CoursesCardListComponent; - let fixture: ComponentFixture; +describe('CoursListComponent', () => { + let component: CoursListComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ CoursesCardListComponent ] + declarations: [ CoursListComponent ] }) .compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(CoursesCardListComponent); + fixture = TestBed.createComponent(CoursListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/cours-list/cours-list.component.ts b/src/app/cours-list/cours-list.component.ts new file mode 100644 index 0000000..19addb8 --- /dev/null +++ b/src/app/cours-list/cours-list.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Course } from '../model/course'; +import { MatDialogConfig, MatDialog } from '@angular/material/dialog'; +import { CourseDialogComponent } from '../course-dialog/course-dialog.component'; +import { filter, tap } from 'rxjs/operators'; + +@Component({ + selector: 'cours-list', + templateUrl: './cours-list.component.html', + styleUrls: ['./cours-list.component.css'] +}) +export class CoursListComponent implements OnInit { + + @Input() + courses: Course[] = []; + + @Output() + private courseOnChange = new EventEmitter(); + + constructor(private dialog: MatDialog) { } + + ngOnInit(): void { + } + + editCourse(course: Course) { + + const dialogConfig = new MatDialogConfig(); + + dialogConfig.disableClose = true; + dialogConfig.autoFocus = true; + dialogConfig.width = "400px"; + + dialogConfig.data = course; + + const dialogRef = this.dialog.open(CourseDialogComponent, dialogConfig); + + dialogRef.afterClosed() + .pipe( + filter(val=> !!val), + tap(()=> this.courseOnChange.emit('')) + ) + .subscribe((ref) => { + }) + } + +} diff --git a/src/app/course-dialog/course-dialog.component.ts b/src/app/course-dialog/course-dialog.component.ts index 6bcaa85..41b713e 100644 --- a/src/app/course-dialog/course-dialog.component.ts +++ b/src/app/course-dialog/course-dialog.component.ts @@ -5,6 +5,7 @@ import {FormBuilder, Validators, FormGroup} from "@angular/forms"; import * as moment from 'moment'; import {catchError} from 'rxjs/operators'; import {throwError} from 'rxjs'; +import { CourseService } from '../shared/course.service'; @Component({ selector: 'course-dialog', @@ -20,7 +21,8 @@ export class CourseDialogComponent implements AfterViewInit { constructor( private fb: FormBuilder, private dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) course:Course) { + @Inject(MAT_DIALOG_DATA) course:Course, + private courseService: CourseService) { this.course = course; @@ -38,8 +40,11 @@ export class CourseDialogComponent implements AfterViewInit { } save() { - const changes = this.form.value; + this.courseService.updateCorse(this.course.id, changes) + .subscribe((res)=> { + this.dialogRef.close(res); + }); } diff --git a/src/app/courses-card-list/courses-card-list.component.html b/src/app/courses-card-list/courses-card-list.component.html deleted file mode 100644 index d9b9e26..0000000 --- a/src/app/courses-card-list/courses-card-list.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - {{course.description}} - - - - - - -

{{course.longDescription}}

-
- - - - - - - - - -
diff --git a/src/app/courses-card-list/courses-card-list.component.ts b/src/app/courses-card-list/courses-card-list.component.ts deleted file mode 100644 index 6e0f5ea..0000000 --- a/src/app/courses-card-list/courses-card-list.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; -import {Course} from '../model/course'; -import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; -import {CourseDialogComponent} from '../course-dialog/course-dialog.component'; -import {filter, tap} from 'rxjs/operators'; - -@Component({ - selector: 'courses-card-list', - templateUrl: './courses-card-list.component.html', - styleUrls: ['./courses-card-list.component.css'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CoursesCardListComponent implements OnInit { - - @Input() - courses: Course[] = []; - - @Output() - private coursesChanged = new EventEmitter(); - - constructor(private dialog: MatDialog) { - - } - - ngOnInit() { - - } - - editCourse(course: Course) { - - const dialogConfig = new MatDialogConfig(); - - dialogConfig.disableClose = true; - dialogConfig.autoFocus = true; - dialogConfig.width = "400px"; - - dialogConfig.data = course; - - const dialogRef = this.dialog.open(CourseDialogComponent, dialogConfig); - - dialogRef.afterClosed() - .pipe( - filter(val => !!val), - tap(() => this.coursesChanged.emit()) - - ) - .subscribe(); - - - } - -} diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index c84039e..cd2f57d 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -4,76 +4,17 @@

All Courses

- + - - - - - {{course.description}} - - - - - - -

{{course.longDescription}}

-
- - - - - - - - - -
- +
+ - - - - - {{course.description}} - - - - - - -

{{course.longDescription}}

-
- - - - - - - - - -
- +
-
- - -
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index f66828d..4626c1a 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -5,6 +5,7 @@ import {catchError, delay, delayWhen, filter, finalize, map, retryWhen, shareRep import {HttpClient} from '@angular/common/http'; import {MatDialog, MatDialogConfig} from '@angular/material/dialog'; import {CourseDialogComponent} from '../course-dialog/course-dialog.component'; +import { CourseService } from '../shared/course.service'; @Component({ @@ -14,44 +15,44 @@ import {CourseDialogComponent} from '../course-dialog/course-dialog.component'; }) export class HomeComponent implements OnInit { - beginnerCourses: Course[]; + beginnerCourses$: Observable; - advancedCourses: Course[]; + advancedCourses$: Observable; - constructor(private http: HttpClient, private dialog: MatDialog) { + constructor(private courseService: CourseService) { } ngOnInit() { + this.reloadCourseList(); + } - this.http.get('/api/courses') - .subscribe( - res => { - - const courses: Course[] = res["payload"].sort(sortCoursesBySeqNo); + reloadCourseList(){ + + const courses$ = this.courseService.getCourse() + .pipe( + map(courses=> courses.sort(sortCoursesBySeqNo)) + ); - this.beginnerCourses = courses.filter(course => course.category == "BEGINNER"); + // manual subscription for testing. + // courses$.subscribe((res)=> { + // console.log('called courses:- ', res) + // }); - this.advancedCourses = courses.filter(course => course.category == "ADVANCED"); + this.beginnerCourses$ = courses$.pipe( + map(courses => courses.filter(course=> course.category =='BEGINNER')) + ); - }); + this.advancedCourses$ = courses$.pipe( + map(courses => courses.filter(course=> course.category=='ADVANCED')) + ); + } - editCourse(course: Course) { - - const dialogConfig = new MatDialogConfig(); - dialogConfig.disableClose = true; - dialogConfig.autoFocus = true; - dialogConfig.width = "400px"; - dialogConfig.data = course; - - const dialogRef = this.dialog.open(CourseDialogComponent, dialogConfig); - - } } diff --git a/src/app/loading/loading.service.ts b/src/app/loading/loading.service.ts deleted file mode 100644 index dc4f1c4..0000000 --- a/src/app/loading/loading.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject, Observable, Subject,of} from 'rxjs'; -import {concatMap, finalize, tap} from 'rxjs/operators'; - - -@Injectable() -export class LoadingService { - - private loadingSubject = new BehaviorSubject(false); - - loading$: Observable = this.loadingSubject.asObservable(); - - constructor() { - console.log("Loading service created ..."); - } - - showLoaderUntilCompleted(obs$: Observable): Observable { - return of(null) - .pipe( - tap(() => this.loadingOn()), - concatMap(() => obs$), - finalize(() => this.loadingOff()) - ); - } - - loadingOn() { - this.loadingSubject.next(true); - - } - - loadingOff() { - this.loadingSubject.next(false); - } - -} diff --git a/src/app/messages/messages.service.ts b/src/app/messages/messages.service.ts deleted file mode 100644 index 16496c0..0000000 --- a/src/app/messages/messages.service.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject, Observable} from 'rxjs'; -import {filter} from 'rxjs/operators'; - - -@Injectable() -export class MessagesService { - - private subject = new BehaviorSubject([]); - - errors$: Observable = this.subject.asObservable() - .pipe( - filter(messages => messages && messages.length > 0) - ); - - showErrors(...errors: string[]) { - this.subject.next(errors); - } - -} diff --git a/src/app/services/auth.store.ts b/src/app/services/auth.store.ts deleted file mode 100644 index be7ec5f..0000000 --- a/src/app/services/auth.store.ts +++ /dev/null @@ -1,52 +0,0 @@ -import {Inject, Injectable} from '@angular/core'; -import {BehaviorSubject, Observable} from 'rxjs'; -import {User} from '../model/user'; -import {map, shareReplay, tap} from 'rxjs/operators'; -import {HttpClient} from '@angular/common/http'; - -const AUTH_DATA = "auth_data"; - -@Injectable({ - providedIn: 'root' -}) -export class AuthStore { - - private subject = new BehaviorSubject(null); - - user$ : Observable = this.subject.asObservable(); - - isLoggedIn$ : Observable; - isLoggedOut$ : Observable; - - constructor(private http: HttpClient) { - - this.isLoggedIn$ = this.user$.pipe(map(user => !!user)); - - this.isLoggedOut$ = this.isLoggedIn$.pipe(map(loggedIn => !loggedIn)); - - const user = localStorage.getItem(AUTH_DATA); - - if (user) { - this.subject.next(JSON.parse(user)); - } - - } - - login(email:string, password:string): Observable { - return this.http.post("/api/login", {email, password}) - .pipe( - tap(user => { - this.subject.next(user); - localStorage.setItem(AUTH_DATA, JSON.stringify(user)); - }), - shareReplay() - ); - } - - logout() { - this.subject.next(null); - localStorage.removeItem(AUTH_DATA); - } - - -} diff --git a/src/app/services/courses.service.ts b/src/app/services/courses.service.ts deleted file mode 100644 index 907703b..0000000 --- a/src/app/services/courses.service.ts +++ /dev/null @@ -1,76 +0,0 @@ -import {Injectable} from '@angular/core'; -import {HttpClient} from '@angular/common/http'; -import {Course} from '../model/course'; -import {Observable} from 'rxjs'; -import {map, shareReplay} from 'rxjs/operators'; -import {Lesson} from '../model/lesson'; - - -@Injectable({ - providedIn:'root' -}) -export class CoursesService { - - constructor(private http:HttpClient) { - - } - - loadCourseById(courseId:number) { - return this.http.get(`/api/courses/${courseId}`) - .pipe( - shareReplay() - ); - } - - loadAllCourseLessons(courseId:number): Observable { - return this.http.get('/api/lessons', { - params: { - pageSize: "10000", - courseId: courseId.toString() - } - }) - .pipe( - map(res => res["payload"]), - shareReplay() - ); - } - - loadAllCourses(): Observable { - return this.http.get("/api/courses") - .pipe( - map(res => res["payload"]), - shareReplay() - ); - } - - - saveCourse(courseId:string, changes: Partial):Observable { - return this.http.put(`/api/courses/${courseId}`, changes) - .pipe( - shareReplay() - ); - } - - - searchLessons(search:string): Observable { - return this.http.get('/api/lessons', { - params: { - filter: search, - pageSize: "100" - } - }) - .pipe( - map(res => res["payload"]), - shareReplay() - ); - } - - -} - - - - - - - diff --git a/src/app/services/courses.store.ts b/src/app/services/courses.store.ts deleted file mode 100644 index e7aba4b..0000000 --- a/src/app/services/courses.store.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject, Observable, throwError} from 'rxjs'; -import {Course, sortCoursesBySeqNo} from '../model/course'; -import {catchError, map, shareReplay, tap} from 'rxjs/operators'; -import {HttpClient} from '@angular/common/http'; -import {LoadingService} from '../loading/loading.service'; -import {MessagesService} from '../messages/messages.service'; - - -@Injectable({ - providedIn: 'root' -}) -export class CoursesStore { - - private subject = new BehaviorSubject([]); - - courses$ : Observable = this.subject.asObservable(); - - constructor( - private http:HttpClient, - private loading: LoadingService, - private messages: MessagesService) { - - this.loadAllCourses(); - - } - - private loadAllCourses() { - - const loadCourses$ = this.http.get('/api/courses') - .pipe( - map(response => response["payload"]), - catchError(err => { - const message = "Could not load courses"; - this.messages.showErrors(message); - console.log(message, err); - return throwError(err); - }), - tap(courses => this.subject.next(courses)) - ); - - this.loading.showLoaderUntilCompleted(loadCourses$) - .subscribe(); - - } - - saveCourse(courseId:string, changes: Partial): Observable { - - const courses = this.subject.getValue(); - - const index = courses.findIndex(course => course.id == courseId); - - const newCourse: Course = { - ...courses[index], - ...changes - }; - - const newCourses: Course[] = courses.slice(0); - - newCourses[index] = newCourse; - - this.subject.next(newCourses); - - return this.http.put(`/api/courses/${courseId}`, changes) - .pipe( - catchError(err => { - const message = "Could not save course"; - console.log(message, err); - this.messages.showErrors(message); - return throwError(err); - }), - shareReplay() - ); - } - - filterByCategory(category: string): Observable { - return this.courses$ - .pipe( - map(courses => - courses.filter(course => course.category == category) - .sort(sortCoursesBySeqNo) - ) - ) - } - -} diff --git a/src/app/shared/course.service.ts b/src/app/shared/course.service.ts new file mode 100644 index 0000000..663933a --- /dev/null +++ b/src/app/shared/course.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from "@angular/core"; +import { HttpClient } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { Course } from "../model/course"; +import { map, shareReplay } from "rxjs/operators"; + +/** + * State less obsrable service, design patter. + */ +@Injectable({ + providedIn: 'root' +}) +export class CourseService { + constructor(private http: HttpClient) { + + } + + getCourse(): Observable { + return this.http.get('/api/courses') + .pipe( + map(res => res['payload']), + shareReplay() + ); + } + + getOtherData(): Observable { + return this.http.get('https://sumit-jaiswal.free.beeceptor.com/users') + .pipe( + shareReplay() + ); + } + + updateCorse(courseId: string, changes: Partial): Observable { + return this.http.put(`/api/courses/${courseId}`, changes); + } +} \ No newline at end of file