Skip to content

Stateless observables services #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ tmp.json
!.vscode/extensions.json

# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
14 changes: 2 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -3,31 +3,21 @@

This repository contains the code of the [Reactive Angular Course](https://angular-university.io/course/reactive-angular-course).

This course repository is updated to Angular v9, and there is a package-lock.json file available, for avoiding semantic versioning installation issues.
This course repository is updated to Angular v15.

![Reactive Angular Course](https://angular-university.s3-us-west-1.amazonaws.com/course-images/reactive-angular-course.jpg)


# Installation pre-requisites

IMPORTANT: Please use the latest Node and especially NPM, to make sure the package-lock.json is used.

For running this project we need and npm installed on our machine. These are some tutorials to install node in different operating systems:

*Its important to install the latest version of Node*

- [Install Node and NPM on Windows](https://www.youtube.com/watch?v=8ODS6RM6x7g)
- [Install Node and NPM on Linux](https://www.youtube.com/watch?v=yUdHk-Dk_BY)
- [Install Node and NPM on Mac](https://www.youtube.com/watch?v=Imj8PgG3bZU)

Please use Node 18 long-term support (LTS) version.

# Installing the Angular CLI

With the following command the angular-cli will be installed globally in your machine:

npm install -g @angular/cli


# How To install this repository

We can install the master branch using the following commands:
59 changes: 12 additions & 47 deletions angular.json
Original file line number Diff line number Diff line change
@@ -3,15 +3,14 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular-material-course": {
"reactive-angular-course": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"aot": true,
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
@@ -24,7 +23,13 @@
"styles": [
"src/styles.scss"
],
"scripts": []
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
@@ -37,9 +42,7 @@
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
@@ -55,18 +58,18 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "angular-material-course:build"
"browserTarget": "reactive-angular-course:build"
},
"configurations": {
"production": {
"browserTarget": "angular-material-course:build:production"
"browserTarget": "reactive-angular-course:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angular-material-course:build"
"browserTarget": "reactive-angular-course:build"
}
},
"test": {
@@ -85,51 +88,13 @@
"src/favicon.ico"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"angular-material-course-e2e": {
"root": "",
"sourceRoot": "",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "angular-material-course:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"e2e/tsconfig.e2e.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "angular-material-course",
"schematics": {
"@schematics/angular:component": {
"styleext": "scss"
"style": "scss"
},
"@schematics/angular:directive": {
"prefix": ""
30,658 changes: 20,829 additions & 9,829 deletions package-lock.json

Large diffs are not rendered by default.

63 changes: 32 additions & 31 deletions package.json
Original file line number Diff line number Diff line change
@@ -4,55 +4,56 @@
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "./node_modules/.bin/ng serve --proxy-config ./proxy.json",
"server": "./node_modules/.bin/ts-node -P ./server/server.tsconfig.json ./server/server.ts",
"start": "ng serve --proxy-config ./proxy.json",
"server": "ts-node -P ./server/server.tsconfig.json ./server/server.ts",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "9.0.0",
"@angular/cdk": "^9.0.0",
"@angular/common": "9.0.0",
"@angular/compiler": "9.0.0",
"@angular/core": "9.0.0",
"@angular/forms": "9.0.0",
"@angular/material": "^9.0.0",
"@angular/material-moment-adapter": "9.0.0",
"@angular/platform-browser": "9.0.0",
"@angular/platform-browser-dynamic": "9.0.0",
"@angular/router": "9.0.0",
"@angular/animations": "15.0.2",
"@angular/cdk": "^15.0.1",
"@angular/common": "15.0.2",
"@angular/compiler": "15.0.2",
"@angular/core": "15.0.2",
"@angular/forms": "15.0.2",
"@angular/material": "^15.0.1",
"@angular/material-moment-adapter": "15.0.1",
"@angular/platform-browser": "15.0.2",
"@angular/platform-browser-dynamic": "15.0.2",
"@angular/router": "15.0.2",
"body-parser": "^1.18.3",
"core-js": "^2.4.1",
"express": "^4.16.2",
"moment": "^2.22.2",
"rxjs": "6.5.4",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
"tslib": "^2.0.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.900.1",
"@angular/cli": "^9.0.1",
"@angular/compiler-cli": "9.0.0",
"@angular/language-service": "9.0.0",
"@angular-devkit/build-angular": "^15.0.2",
"@angular/cli": "^15.0.2",
"@angular/compiler-cli": "15.0.2",
"@angular/language-service": "15.0.2",
"@types/express": "^4.0.39",
"@types/jasmine": "~2.5.53",
"@types/jasmine": "~3.8.0",
"@types/jasminewd2": "~2.0.2",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "^4.1.0",
"karma-chrome-launcher": "~2.1.1",
"jasmine-core": "~3.8.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.2",
"karma-chrome-launcher": "~3.1.0",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "^6.0.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~3.2.0",
"tslint": "~5.7.0",
"typescript": "3.7.5"
"typescript": "4.8.4"
},
"engines": {
"node": "16"
}
}
2 changes: 1 addition & 1 deletion proxy.json
Original file line number Diff line number Diff line change
@@ -3,4 +3,4 @@
"target": "http://localhost:9000",
"secure": false
}
}
}
2 changes: 1 addition & 1 deletion server/login.route.ts
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ export function loginUser(req: Request, res: Response) {
const user = authenticate(email, password);

if (user) {
res.status(200).json(user);
res.status(200).json({email: user.email});
}
else {
res.sendStatus(403);
13 changes: 10 additions & 3 deletions server/search-lessons.route.ts
Original file line number Diff line number Diff line change
@@ -9,15 +9,22 @@ import {setTimeout} from "timers";

export function searchLessons(req: Request, res: Response) {

const queryParams = req.query;
const queryParams = req.query as any;

const courseId = queryParams.courseId,
filter = queryParams.filter || '',
sortOrder = queryParams.sortOrder || 'asc',
pageNumber = parseInt(queryParams.pageNumber) || 0,
pageSize = parseInt(queryParams.pageSize) || 3;

let lessons = Object.values(LESSONS).filter(lesson => lesson.courseId == courseId).sort((l1, l2) => l1.id - l2.id);
let lessons;

if (courseId) {
lessons = Object.values(LESSONS).filter(lesson => lesson.courseId == courseId).sort((l1, l2) => l1.id - l2.id);
}
else {
lessons = Object.values(LESSONS);
}

if (filter) {
lessons = lessons.filter(lesson => lesson.description.trim().toLowerCase().search(filter.toLowerCase()) >= 0);
@@ -36,4 +43,4 @@ export function searchLessons(req: Request, res: Response) {
},1000);


}
}
4 changes: 4 additions & 0 deletions server/server.ts
Original file line number Diff line number Diff line change
@@ -12,6 +12,10 @@ const app: Application = express();

app.use(bodyParser.json());

const cors = require('cors');

app.use(cors({origin: true}));

app.route('/api/courses').get(getAllCourses);

app.route('/api/courses/:id').get(getCourseById);
2 changes: 1 addition & 1 deletion src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ const routes: Routes = [
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes, {})],
exports: [RouterModule]
})
export class AppRoutingModule {
13 changes: 8 additions & 5 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -25,14 +25,15 @@ import {MatTableModule} from '@angular/material/table';
import {MatToolbarModule} from '@angular/material/toolbar';
import {HttpClientModule} from '@angular/common/http';
import {CourseDialogComponent} from './course-dialog/course-dialog.component';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {ReactiveFormsModule} from '@angular/forms';
import {MatMomentDateModule} from '@angular/material-moment-adapter';
import {LoginComponent} from './login/login.component';
import {LessonComponent} from './lesson/lesson.component';
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';

@NgModule({
declarations: [
@@ -46,13 +47,17 @@ import { LoadingComponent } from './loading/loading.component';
SafeUrlPipe,
MessagesComponent,
SearchLessonsComponent,
LoadingComponent
LoadingComponent,
CoursesCardListComponent

],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
AppRoutingModule,
ReactiveFormsModule,

MatMenuModule,
MatButtonModule,
MatIconModule,
@@ -67,11 +72,9 @@ import { LoadingComponent } from './loading/loading.component';
MatSortModule,
MatProgressSpinnerModule,
MatDialogModule,
AppRoutingModule,
MatSelectModule,
MatDatepickerModule,
MatMomentDateModule,
ReactiveFormsModule
MatMomentDateModule
],
providers: [
],
7 changes: 4 additions & 3 deletions src/app/course-dialog/course-dialog.component.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@


.mat-form-field {
display: block;
mat-form-field {
display: block;
width: 100%;
}

textarea {
height: 100px;
resize: vertical;
}
}
10 changes: 5 additions & 5 deletions src/app/course-dialog/course-dialog.component.html
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ <h2 mat-dialog-title>{{course.description}}</h2>
placeholder="Course Description"
formControlName="description">

</mat-form-field>
</mat-form-field>

<mat-form-field>

@@ -18,11 +18,11 @@ <h2 mat-dialog-title>{{course.description}}</h2>

<mat-option value="BEGINNER">
Beginner
</mat-option>
</mat-option>
<mat-option value="INTERMEDIATE">
Intermediate
</mat-option>
<mat-option value="ADVANCED">
<mat-option value="ADVANCED">
Advanced
</mat-option>

@@ -58,12 +58,12 @@ <h2 mat-dialog-title>{{course.description}}</h2>

<mat-dialog-actions>

<button class="mat-raised-button"
<button mat-raised-button
(click)="close()">
Close
</button>

<button class="mat-raised-button mat-primary" #saveButton (click)="save()">
<button mat-raised-button color="primary" #saveButton (click)="save()">
Save
</button>

30 changes: 21 additions & 9 deletions src/app/course-dialog/course-dialog.component.ts
Original file line number Diff line number Diff line change
@@ -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 { CoursesService } from '../service/courses.service';

@Component({
selector: 'course-dialog',
@@ -14,17 +15,19 @@ import {throwError} from 'rxjs';
export class CourseDialogComponent implements AfterViewInit {

form: FormGroup;

course:Course;
course:Course;

constructor(
private fb: FormBuilder,
private dialogRef: MatDialogRef<CourseDialogComponent>,
@Inject(MAT_DIALOG_DATA) course:Course) {
// course:Course perfaqeson objektin me te dhana qe marim nga funkjoni editCourse(course: Course) { " home.component.ts "
@Inject(MAT_DIALOG_DATA) course:Course,
private coursesService: CoursesService) { // course:Course perfaqeson

this.course = course;

this.form = fb.group({
this.form = fb.group({
// te dhenat brenda objektin, i bashkangjisim brenda inputeve specifike
description: [course.description, Validators.required],
category: [course.category, Validators.required],
releasedAt: [moment(), Validators.required],
@@ -37,12 +40,21 @@ export class CourseDialogComponent implements AfterViewInit {

}

save() {

const changes = this.form.value;

save() {
// marim ndryshimet nga FormGroup
const changes = this.form.value;

this.coursesService.saveCourse(this.course.id, changes).subscribe((response: any) => {
console.log("response when save change by id", response);
// objektin e vlerave qe dergojme ne servis i dergojme edhe ne funksjonin dialogRef te courses-card-list.component.ts
// ku me ndimen e tap() aktivizojme nje fuksjon bosh " this.coursesChanges.emit() ", i cili
// na mundson ti bej reloud funkjonit (coursesChanges)="reloadCourses()" ne home.component.html
this.dialogRef.close(response);
})


}

close() {
this.dialogRef.close();
}
15 changes: 15 additions & 0 deletions src/app/courses-card-list/courses-card-list.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@


.course-card {
margin: 20px 10px;
}

.course-actions {
display: flex;
justify-content: center;
}

.course-actions button {
margin-right: 5px;
}

33 changes: 33 additions & 0 deletions src/app/courses-card-list/courses-card-list.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@


<mat-card *ngFor="let course of courses" class="course-card mat-elevation-z10">

<mat-card-header>

<mat-card-title>{{course.description}}</mat-card-title>

</mat-card-header>

<img mat-card-image [src]="course.iconUrl">

<mat-card-content>
<p>{{course.longDescription}}</p>
</mat-card-content>

<mat-card-actions class="course-actions">

<button mat-button
mat-raised-button
color="primary"
[routerLink]="['/courses', course.id]">
VIEW COURSE
</button>

<button mat-button mat-raised-button color="accent"
(click)="editCourse(course)">
EDIT
</button>

</mat-card-actions>

</mat-card>
17 changes: 17 additions & 0 deletions src/app/courses-card-list/courses-card-list.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@


.course-card {
margin: 20px 10px;
}

.course-actions {
display: flex;
justify-content: center;
}


.course-actions button {
display: flex;
justify-content: center;
margin-right: 5px;
}
23 changes: 23 additions & 0 deletions src/app/courses-card-list/courses-card-list.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CoursesCardListComponent } from './courses-card-list.component';

describe('CoursesCardListComponent', () => {
let component: CoursesCardListComponent;
let fixture: ComponentFixture<CoursesCardListComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CoursesCardListComponent ]
})
.compileComponents();

fixture = TestBed.createComponent(CoursesCardListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
60 changes: 60 additions & 0 deletions src/app/courses-card-list/courses-card-list.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { outputAst } from '@angular/compiler';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { filter, tap } from 'rxjs/operators';

import { CourseDialogComponent } from '../course-dialog/course-dialog.component';
import { Course } from '../model/course';

@Component({
selector: 'courses-card-list',
templateUrl: './courses-card-list.component.html',
styleUrls: ['./courses-card-list.component.scss']
})
export class CoursesCardListComponent implements OnInit {

@Input() courses: Course[] = [];

@Output() coursesChanges = new EventEmitter();

constructor( private dialog: MatDialog ) {}

ngOnInit() {

}

// marim vetem objektin specifik qe duam
editCourse(course: Course) {

const dialogConfig = new MatDialogConfig();

dialogConfig.disableClose = true;
dialogConfig.autoFocus = true;
dialogConfig.width = "400px";

dialogConfig.data = course;

// objektin specifik e trasportojme ne componentin CourseDialogComponent
const dialogRef = this.dialog.open(CourseDialogComponent, dialogConfig);

// pas mbylljes se dialogut marim response nga " this.dialogRef.close(response); "
// du ke shtuar nje event bosh " this.coursesChanges.emit() ",
// i cili na mundeson ti bej reload faqes ne kohe reale

// **** nese do provojme nese e fshim, na vin te dhenat ne kohe reale ????????????
dialogRef.afterClosed().pipe(

filter( val => !!val), // i bejme reload " reloadCourses() funstions " kur servisi " this.coursesService.saveCourse " eshte succesfully
// na mundeson ti bejme reload funksjonit (coursesChanges)="reloadCourses()" ne home.component.html
// i cili na shfaq te gjitha cards.
tap(() => {
this.coursesChanges.emit();
console.log("Reaload reloadCourses() funstions...")
}
)).subscribe((data: any) => {
console.log("response after close the dialog", data);
});

}

}
9 changes: 0 additions & 9 deletions src/app/home/home.component.css
Original file line number Diff line number Diff line change
@@ -7,12 +7,3 @@




.course-card {
margin: 20px 10px;
}

.course-actions {
text-align: center;
}

74 changes: 17 additions & 57 deletions src/app/home/home.component.html
Original file line number Diff line number Diff line change
@@ -5,75 +5,35 @@ <h3>All Courses</h3>

<mat-tab-group>

<!-- mat tab 1 -->
<mat-tab label="Beginners">

<!-- (beginnerCourses$ | async) " async -> na mundeson ti bejme ne menyre atomanike subscribe dhe unsubscribe ketu ne html"-->

<mat-card *ngFor="let course of beginnerCourses" class="course-card mat-elevation-z10">

<mat-card-header>

<mat-card-title>{{course.description}}</mat-card-title>

</mat-card-header>

<img mat-card-image [src]="course.iconUrl">

<mat-card-content>
<p>{{course.longDescription}}</p>
</mat-card-content>

<mat-card-actions class="course-actions">

<button mat-button class="mat-raised-button mat-primary" [routerLink]="['/courses', course.id]">
VIEW COURSE
</button>

<button mat-button class="mat-raised-button mat-accent"
(click)="editCourse(course)">
EDIT
</button>

</mat-card-actions>

</mat-card>
<!-- responsei qe marim nga " dialogRef.afterClosed() " duke shtuar nje thirje ne " this.coursesChanges.emit() " -->
<!-- ku thirja na ndimon ti bejme reload serviseve qe kemi ne funkjonin " reloadCourses() ", me ndimen e: -->
<!-- @Output() coursesChanges = new EventEmitter(); qe trasforton thirjen nga komponenti femi - prinde -->

<courses-card-list [courses]=" beginnerCourses$ | async"
(coursesChanges)="reloadCourses()">
</courses-card-list>

</mat-tab>

<mat-tab label="Advanced">

<mat-card *ngFor="let course of advancedCourses" class="course-card mat-elevation-z10">

<mat-card-header>

<mat-card-title>{{course.description}}</mat-card-title>

</mat-card-header>

<img mat-card-image [src]="course.iconUrl">

<mat-card-content>
<p>{{course.longDescription}}</p>
</mat-card-content>

<mat-card-actions class="course-actions">

<button mat-button class="mat-raised-button mat-primary" [routerLink]="['/courses', course.id]">
VIEW COURSE
</button>

<button mat-button class="mat-raised-button mat-accent"
(click)="editCourse(course)">
EDIT
</button>

</mat-card-actions>
<!-- mat tab 2 -->
<mat-tab label="Advanced">

</mat-card>
<courses-card-list [courses]=" advancedCourses$ | async"
(coursesChanges)="reloadCourses()">
</courses-card-list>

</mat-tab>

</mat-tab-group>



</div>


82 changes: 51 additions & 31 deletions src/app/home/home.component.ts
Original file line number Diff line number Diff line change
@@ -2,56 +2,76 @@ 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';
import { CoursesService } from '../service/courses.service';


@Component({
selector: 'home',
selector: 'home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

beginnerCourses: Course[];
beginnerCourses$: Observable<Course[]>;

advancedCourses$: Observable<Course[]>;

advancedCourses: Course[];


constructor(private http: HttpClient, private dialog: MatDialog) {
constructor(
private coursesService: CoursesService
) {

}

ngOnInit() {

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");

});

this.reloadCourses();

}

reloadCourses() {
const courses$ = this.coursesService.loadAllCourse()
.pipe(
// map mer objeket nje nga nje, te cilat jan Observable array
map( courses => courses.sort(sortCoursesBySeqNo))
);

this.beginnerCourses$ = courses$
.pipe(
map( courses => courses.filter(course => course.category == "BEGINNER"))
);

this.advancedCourses$ = courses$
.pipe(
map( courses => courses.filter(course => course.category == "ADVANCED") )
);



// this.http.get('/api/courses')
// .subscribe(
// res => {

// // res == { payload: Array(14) }
// // res["payload"] == [ {..}, {..}, ... {..} ] -> 14 objekte brenda array
// // duke qen se " res["payload"] " eshte nje array me objekte brenda, ku .sort():
// // na mundesojme te kryejme nje funksjonin " sortCoursesBySeqNo "
// // i cili na drentit objekte nga me i vogli te me i madhi.
// const courses: Course[] = res["payload"].sort(sortCoursesBySeqNo);

// // filter i ben return nje array duke kryer nje funkjon brenda kllapave
// // ne rastin ton po diferencojme objketet qe e kan course.category == "BEGINNER" me course.category == "ADVANCED"
// this.beginnerCourses = courses.filter(course => course.category == "BEGINNER"); // 11 objekte brenda array
// this.advancedCourses = courses.filter(course => course.category == "ADVANCED"); // 3 objekte brenda array

// console.log("this.beginnerCourses", this.beginnerCourses);
// console.log("this.advancedCourses", this.advancedCourses);
// });
}

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);

}


}

6 changes: 1 addition & 5 deletions src/app/lesson/lesson.component.ts
Original file line number Diff line number Diff line change
@@ -5,11 +5,7 @@ import { Component, OnInit } from '@angular/core';
templateUrl: './lesson.component.html',
styleUrls: ['./lesson.component.css']
})
export class LessonComponent implements OnInit {
export class LessonComponent {

constructor() { }

ngOnInit(): void {
}

}
6 changes: 6 additions & 0 deletions src/app/model/course.ts
Original file line number Diff line number Diff line change
@@ -12,6 +12,12 @@ export interface Course {
}


// funskjoni na mundeson renditjen e objekteve nga me i vogli te me i madhi
// ku mer 2 objekte e para brenda nje array, dhe i rendit sipas numra vete " seqNo "
// po te kontrollosh te dhenat http://localhost:9000/api/courses, objektet brenda array jan te cregullt si pas renditjes se numrave " seqNo "
// me ndimen e ket funksjoni thjesh rendisim objekte brenda array
export function sortCoursesBySeqNo(c1: Course, c2: Course) {
return c1.seqNo - c2.seqNo;
}


4 changes: 3 additions & 1 deletion src/app/search-lessons/search-lessons.component.css
Original file line number Diff line number Diff line change
@@ -32,7 +32,9 @@
margin-top: 10px;
}


.back-btn {
margin-top: 30px;
}

.search-bar {
margin-top: 20px;
32 changes: 32 additions & 0 deletions src/app/service/courses.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

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';

@Injectable({
providedIn: 'root'
})
export class CoursesService {

constructor(private http: HttpClient ) { }

// shareReplay() -> minimizon observable subscription ne XHR Network, nese e fshim do na paraqiten 2,
// sepse kemi 2 subscription, this.beginnerCourses$ dhe this.advancedCourses$

loadAllCourse(): Observable<Course[]> {
return this.http.get<Course[]>('/api/courses')
.pipe(
map(res => res["payload"]),
shareReplay()
);
}

saveCourse(courseId: string, changes: Partial<Course>): Observable<any> {
return this.http.put(`/api/courses/${courseId}`, changes).pipe(
shareReplay()
);
}

}
13 changes: 1 addition & 12 deletions src/polyfills.ts
Original file line number Diff line number Diff line change
@@ -34,9 +34,6 @@
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';

@@ -46,18 +43,10 @@



/**
* Required to support Web Animations `@angular/platform-browser/animations`.
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.



/***************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'zone.js'; // Included with Angular CLI.



92 changes: 54 additions & 38 deletions src/styles.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
/* You can add global styles to this file, and also import other style files */
@use '@angular/material' as mat;

html, body { height: 100%; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }


@import "~@angular/material/theming";
@include mat.core();

// Include non-theme styles for core.
@include mat-core();
$dark-primary-text: rgba(black, 0.87);

$light-primary-text: white;

$mat-primary: (
50: #fce4ec,
@@ -23,20 +26,20 @@ $mat-primary: (
A400: #f50057,
A700: #c51162,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $dark-primary-text,
400: $dark-primary-text,
500: $light-primary-text,
600: $light-primary-text,
700: $light-primary-text,
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $light-primary-text,
A400: $light-primary-text,
A700: $light-primary-text,
50: rgba(black, 0.87),
100: rgba(black, 0.87),
200: rgba(black, 0.87),
300: rgba(black, 0.87),
400: rgba(black, 0.87),
500: white,
600: white,
700: white,
800: white,
900: white,
A100: rgba(black, 0.87),
A200: white,
A400: white,
A700: white,
)
);

@@ -56,29 +59,42 @@ $mat-accent: (
A400: #3c35dd,
A700: #6200ea,
contrast: (
50: $dark-primary-text,
100: $dark-primary-text,
200: $dark-primary-text,
300: $light-primary-text,
400: $light-primary-text,
500: $light-primary-text,
600: $light-primary-text,
700: $light-primary-text,
800: $light-primary-text,
900: $light-primary-text,
A100: $dark-primary-text,
A200: $light-primary-text,
A400: $light-primary-text,
A700: $light-primary-text,
50: rgba(black, 0.87),
100: rgba(black, 0.87),
200: rgba(black, 0.87),
300: white,
400: white,
500: white,
600: white,
700: white,
800: white,
900: white,
A100: rgba(black, 0.87),
A200: white,
A400: white,
A700: white,
)
);

$primary: mat.define-palette($mat-primary, 500);

$accent: mat.define-palette($mat-accent, A200, A100, A400);

$theme: mat.define-light-theme((
color: (
primary: $primary,
accent: $accent,
),
typography: mat.define-typography-config(),
density: 0,
));

// Define a theme.
$primary: mat-palette($mat-pink);
$accent: mat-palette($mat-accent, A200, A100, A400);
@include mat.all-component-themes($theme);

$theme: mat-light-theme($primary, $accent);
.mat-elevation-z7 {
box-shadow: 0 4px 5px -2px #0003, 0 7px 10px 1px #00000024, 0 2px 16px 1px #0000001f !important;
}

// Include all theme styles for the components.
@include angular-material-theme($theme);
.mat-elevation-z10 {
box-shadow: 0 6px 6px -3px #0003, 0 10px 14px 1px #00000024, 0 4px 18px 3px #0000001f !important;
}
9 changes: 3 additions & 6 deletions src/test.ts
Original file line number Diff line number Diff line change
@@ -14,19 +14,16 @@ import {

// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
declare const __karma__: any;
declare const require: any;

// Prevent Karma from running prematurely.
__karma__.loaded = function () {};

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
platformBrowserDynamicTesting(), {
teardown: { destroyAfterEach: false }
}
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();
6 changes: 3 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -6,16 +6,16 @@
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es2015",
"target": "ES2022",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom",
"ES2017.object"
]
],
"useDefineForClassFields": false
}
}
4 changes: 3 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
@@ -11,6 +11,9 @@
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warning"
},
"eofline": true,
"forin": true,
"import-blacklist": [
@@ -69,7 +72,6 @@
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [