diff --git a/package-lock.json b/package-lock.json index 8fa252e..63c0bed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "angular13-fundamentals-workshop", "version": "0.0.0", "dependencies": { "@angular/animations": "~13.0.0", @@ -15824,7 +15825,9 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "requires": {} + "requires": { + "ajv": "^8.0.0" + } }, "ansi-align": { "version": "3.0.1", diff --git a/server/db.json b/server/db.json index 4f0a3cb..1eccb59 100644 --- a/server/db.json +++ b/server/db.json @@ -1,11 +1,24 @@ { "courses": [ { - "id": 1, + "id": "1", "title": "Angular 13 Fundamentals", "description": "Learn the fundamentals of Angular 13", "percentComplete": 26, "favorite": true + }, + { + "id": "PZi4n6c", + "title": "java core", + "description": "Trying to learn new skills", + "percentComplete": 0, + "favorite": true + } + ], + "lessons": [ + { + "id": "2", + "title": "Componenets Driven Architecture" } ] } \ No newline at end of file diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..05e29bb --- /dev/null +++ b/src/README.md @@ -0,0 +1,6 @@ +# Angular 13 +## Common folder for shared state. +- Template Driven Form: create interface - `ng g interface common/models/course` +- Services: create using `ng g s common/services/courses` +- Lessons service: `ng g s common/services/lessons` +- Component driven architecture: Stateflow parents class components & Event binding from presentations child components`ng g c courses/courses-list -m app.module.ts -d` and `ng g c courses/course-details -m app.module.ts` \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index 028469b..c9d9c5a 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -19,6 +19,12 @@ +
diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d2c7873..dfec96d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -8,16 +8,20 @@ import { AppComponent } from './app.component'; import { MaterialModule } from './material.module'; import { HomeComponent } from './home/home.component'; import { CoursesComponent } from './courses/courses.component'; +import { FormsModule } from '@angular/forms'; +import { CoursesListComponent } from './courses/courses-list/courses-list.component'; +import { CourseDetailsComponent } from './courses/course-details/course-details.component'; @NgModule({ imports: [ BrowserModule, AppRoutingModule, BrowserAnimationsModule, + FormsModule, MaterialModule, HttpClientModule, ], - declarations: [AppComponent, HomeComponent, CoursesComponent], + declarations: [AppComponent, HomeComponent, CoursesComponent, CoursesListComponent, CourseDetailsComponent], providers: [], bootstrap: [AppComponent], }) diff --git a/src/app/common/models/course.ts b/src/app/common/models/course.ts new file mode 100644 index 0000000..d19f4c0 --- /dev/null +++ b/src/app/common/models/course.ts @@ -0,0 +1,7 @@ +export interface Course { + id: string; + title: string; + description: string; + percentComplete: number; + favorite: boolean; +} diff --git a/src/app/common/services/courses.service.spec.ts b/src/app/common/services/courses.service.spec.ts new file mode 100644 index 0000000..b4315a2 --- /dev/null +++ b/src/app/common/services/courses.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { CoursesService } from './courses.service'; + +describe('CoursesService', () => { + let service: CoursesService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(CoursesService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/common/services/courses.service.ts b/src/app/common/services/courses.service.ts new file mode 100644 index 0000000..7c629f8 --- /dev/null +++ b/src/app/common/services/courses.service.ts @@ -0,0 +1,67 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Course } from '../models/course'; + +const BASE_URL = 'http://localhost:3000' + +@Injectable({ + providedIn: 'root', +}) +export class CoursesService { + model = 'courses'; + + constructor(private http: HttpClient){} + + all() { + return this.http.get(this.getUrl()); + } + + find(id) { + return this.http.get(this.getUrlWithID(id)); + } + + create(course) { + return this.http.post(this.getUrl(), course) + } + + update(course) { + return this.http.put(this.getUrlWithID(course.id), course); + } + + delete(id) { + return this.http.delete(this.getUrlWithID(id)); + } + + private getUrl() { + return `${BASE_URL}/${this.model}`; + } + + private getUrlWithID(id) { + return `${this.getUrl()}/${id}`; + } + + + // courses: Course[] = [ + // { + // id: '1', + // title: 'Angular 13 Fundamentals', + // description: 'Learn the fundamentals of Angular 13', + // percentComplete: 66, + // favorite: true, + // }, + // { + // id: '2', + // title: 'Javascript Fundamentals', + // description: 'Learn the Javascript fundamentals of Angular 13', + // percentComplete: 98, + // favorite: true, + // }, + // { + // id: '3', + // title: 'Python Fundamentals', + // description: 'Learn the Python fundamentals ', + // percentComplete: 78, + // favorite: true, + // }, + // ]; +} diff --git a/src/app/common/services/lessons.service.spec.ts b/src/app/common/services/lessons.service.spec.ts new file mode 100644 index 0000000..0bbe5ab --- /dev/null +++ b/src/app/common/services/lessons.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { LessonsService } from './lessons.service'; + +describe('LessonsService', () => { + let service: LessonsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(LessonsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/common/services/lessons.service.ts b/src/app/common/services/lessons.service.ts new file mode 100644 index 0000000..f9bf38d --- /dev/null +++ b/src/app/common/services/lessons.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { from } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class LessonsService { + lessons = [ + { title: 'Hello Angular' }, + { title: 'Component Fundamentals' }, + { title: 'Template Driven Forms' }, + { title: 'Angular Services' }, + { title: 'Server Communication' }, + { title: 'Component Driven Architecture' }, + { title: 'Angular Routing' }, + { title: 'Unit Testing Fundamentals' }, + ]; + lessons$ = from(this.lessons); +} diff --git a/src/app/courses/course-details/course-details.component.html b/src/app/courses/course-details/course-details.component.html new file mode 100644 index 0000000..c238ac0 --- /dev/null +++ b/src/app/courses/course-details/course-details.component.html @@ -0,0 +1,42 @@ + + + + + {{originalTitle | titlecase}} + + Select a Course + + + +
+ + + + + + + +
+

{{currentCourse.percentComplete}}% Complete

+ + +
+
+ + Favorate + +
+
+ + + + + + +
+
+
{{form.value | json}}
+
{{form.valid | json}}
+
diff --git a/src/app/courses/course-details/course-details.component.scss b/src/app/courses/course-details/course-details.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/courses/course-details/course-details.component.spec.ts b/src/app/courses/course-details/course-details.component.spec.ts new file mode 100644 index 0000000..2841b53 --- /dev/null +++ b/src/app/courses/course-details/course-details.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CourseDetailsComponent } from './course-details.component'; + +describe('CourseDetailsComponent', () => { + let component: CourseDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CourseDetailsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CourseDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/courses/course-details/course-details.component.ts b/src/app/courses/course-details/course-details.component.ts new file mode 100644 index 0000000..245de33 --- /dev/null +++ b/src/app/courses/course-details/course-details.component.ts @@ -0,0 +1,22 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Course } from 'src/app/common/models/course'; + +@Component({ + selector: 'app-course-details', + templateUrl: './course-details.component.html', + styleUrls: ['./course-details.component.scss'], +}) +export class CourseDetailsComponent { + currentCourse: Course; + originalTitle = ''; + + @Output() saved = new EventEmitter(); + @Output() cancelled = new EventEmitter(); + + // break the sharing + @Input() set course(value) { + if (!value) return + this.currentCourse = { ...value } + this.originalTitle = this.currentCourse.title; + } +} diff --git a/src/app/courses/courses-list/courses-list.component.html b/src/app/courses/courses-list/courses-list.component.html new file mode 100644 index 0000000..9208863 --- /dev/null +++ b/src/app/courses/courses-list/courses-list.component.html @@ -0,0 +1,22 @@ +

COURES LIST

+ + + + Course List + + + + + +

{{course.title | titlecase }}

+

+ {{course.description}} +

+ + +
+
+
+
diff --git a/src/app/courses/courses-list/courses-list.component.scss b/src/app/courses/courses-list/courses-list.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/courses/courses-list/courses-list.component.spec.ts b/src/app/courses/courses-list/courses-list.component.spec.ts new file mode 100644 index 0000000..fb37a60 --- /dev/null +++ b/src/app/courses/courses-list/courses-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoursesListComponent } from './courses-list.component'; + +describe('CoursesListComponent', () => { + let component: CoursesListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CoursesListComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CoursesListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/courses/courses-list/courses-list.component.ts b/src/app/courses/courses-list/courses-list.component.ts new file mode 100644 index 0000000..57078ee --- /dev/null +++ b/src/app/courses/courses-list/courses-list.component.ts @@ -0,0 +1,14 @@ +import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { Course } from 'src/app/common/models/course'; + + +@Component({ + selector: 'app-courses-list', + templateUrl: './courses-list.component.html', + styleUrls: ['./courses-list.component.scss'], +}) +export class CoursesListComponent { + @Input() courses: Course[] = []; + @Output() selected = new EventEmitter(); + @Output() deleted = new EventEmitter(); +} diff --git a/src/app/courses/courses.component.html b/src/app/courses/courses.component.html index 808c586..e460ee0 100644 --- a/src/app/courses/courses.component.html +++ b/src/app/courses/courses.component.html @@ -1,36 +1,10 @@
-
- - - - Course List - - - - - - - - -
- -
- - - - Select Course - - - - - - - - - - - - - -
-
\ No newline at end of file +
+ + +
+
+ + +
+
diff --git a/src/app/courses/courses.component.ts b/src/app/courses/courses.component.ts index 66ceeea..cdbac2c 100644 --- a/src/app/courses/courses.component.ts +++ b/src/app/courses/courses.component.ts @@ -1,24 +1,69 @@ import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Course } from '../common/models/course'; +import { CoursesService } from '../common/services/courses.service'; + +const emptyCourse: Course = { + id: null, + title: '', + description: "", + percentComplete: 0, + favorite: false +} @Component({ selector: 'app-courses', templateUrl: './courses.component.html', - styleUrls: ['./courses.component.scss'] + styleUrls: ['./courses.component.scss'], }) export class CoursesComponent implements OnInit { - courses = [ - { - id: 1, - title: 'Angular 13 Fundamentals', - description: 'Learn the fundamentals of Angular 13', - percentComplete: 26, - favorite: true - } - ]; + courses = []; + courses$: any + selectedCourse = emptyCourse; + originalTitle = ''; - constructor() { } + constructor(private CoursesService: CoursesService) {} ngOnInit(): void { + //this.courses = this.CoursesService.courses; + this.fetchCourses(); + } + + selectCourse(course) { + this.selectedCourse = course; + } + + fetchCourses() { + this.courses$ = this.CoursesService.all() + // this.CoursesService.all().subscribe( + // (result: any) => (this.courses = result) + // ); + } + + saveCourse(course) { + if (course.id) { + this.updateCourse(course); + } else { + this.createCourse(course); + } + } + + createCourse(course) { + this.CoursesService.create(course).subscribe(result => this.fetchCourses()) + } + + updateCourse(course) { + this.CoursesService.update(course).subscribe((result) => + this.fetchCourses() + ); + } + + deleteCourse(courseId) { + console.log('DELETE COURSE', courseId); + } + + reset() { + this.selectCourse({ ...emptyCourse }); } } diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index f426908..3b6855f 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -4,5 +4,10 @@ + + + {{lesson.title}} + + diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 8685181..95d9d4c 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { LessonsService } from '../common/services/lessons.service'; @Component({ selector: 'app-home', @@ -6,18 +7,23 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./home.component.scss'], }) export class HomeComponent implements OnInit { - courseLessons = [ - { title: 'Hello Angular' }, - { title: 'Component Fundamentals' }, - { title: 'Template Driven Forms' }, - { title: 'Angular Services' }, - { title: 'Server Communication' }, - { title: 'Component Driven Architecture' }, - { title: 'Angular Routing' }, - { title: 'Unit Testing Fundamentals' }, - ]; - constructor() {} + lessons$; + lessons = []; + selectedLesson = null; - ngOnInit() {} + constructor(private lessonsService: LessonsService) {} + + ngOnInit() { + this.lessons = this.lessonsService.lessons; + this.lessons$ = this.lessonsService.lessons$; + } + + selectLesson(lesson) { + this.selectedLesson = lesson; + } + + deleteCourse(lessonId) { + console.log('DELETE COURSE', lessonId); + } }