diff --git a/angular.json b/angular.json index 7a4f1ef7..be4c78d3 100644 --- a/angular.json +++ b/angular.json @@ -94,5 +94,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 02972627..c1db6726 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,7 +1,17 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { ListComponent } from './contacts/list/list.component'; +import { AddComponent } from './contacts/add/add.component'; +import { ViewComponent } from './contacts/view/view.component'; +import { EditComponent } from './contacts/edit/edit.component'; -const routes: Routes = []; + +const routes: Routes = [ + { path: 'contacts', component: ListComponent }, + { path: 'contacts/add', component: AddComponent }, + { path: 'contacts/:id', component: ViewComponent }, + { path: 'contacts/edit/:id', component: EditComponent }, +]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/src/app/app.component.css b/src/app/app.component.css index 31d9e249..e69de29b 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -1,9 +0,0 @@ -:host { - display: flex; -} - -.page { - flex: 1; - display: flex; - justify-content: center; -} diff --git a/src/app/app.component.html b/src/app/app.component.html index 17aaa0c6..cd9aeee2 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,4 +1,14 @@ -
- +
+
+

Contact Application

+

This really has pet information

+
+
+
+
+
+ +
+
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts new file mode 100644 index 00000000..ee5230ea --- /dev/null +++ b/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterModule } from '@angular/router'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterModule.forRoot([]) + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'angular-pet-workshop-0'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('angular-pet-workshop-0'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, angular-adress-book'); + }); +}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b82791ab..62948c32 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -3,8 +3,8 @@ import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + styleUrl: './app.component.css' }) export class AppComponent { - title = 'angular-address-book'; + title = 'angular-pet-workshop-0'; } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8207184c..9d76b3d1 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,10 +4,11 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { LayoutModule } from './layout/layout.module'; +import { HttpClientModule } from '@angular/common/http'; @NgModule({ declarations: [AppComponent], - imports: [BrowserModule, AppRoutingModule, LayoutModule], + imports: [BrowserModule, AppRoutingModule, LayoutModule, HttpClientModule], bootstrap: [AppComponent], }) -export class AppModule {} +export class AppModule {} \ No newline at end of file diff --git a/src/app/contacts/add/add.component.css b/src/app/contacts/add/add.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/contacts/add/add.component.html b/src/app/contacts/add/add.component.html new file mode 100644 index 00000000..1d1061d1 --- /dev/null +++ b/src/app/contacts/add/add.component.html @@ -0,0 +1,78 @@ +
+

Add Contact

+
+
+
+
+
+
+

Add Contact

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+
+
+
diff --git a/src/app/contacts/add/add.component.spec.ts b/src/app/contacts/add/add.component.spec.ts new file mode 100644 index 00000000..f4528470 --- /dev/null +++ b/src/app/contacts/add/add.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddComponent } from './add.component'; + +describe('AddComponent', () => { + let component: AddComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AddComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/add/add.component.ts b/src/app/contacts/add/add.component.ts new file mode 100644 index 00000000..a66ccef3 --- /dev/null +++ b/src/app/contacts/add/add.component.ts @@ -0,0 +1,58 @@ +import { Component, inject } from '@angular/core'; +import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; +import {ContactsService} from "../contacts.service"; +import {Router} from "@angular/router"; +import { Contact } from '../models/contact'; + +@Component({ + selector: 'app-add', + standalone: true, + imports: [ + ReactiveFormsModule + ], + templateUrl: './add.component.html', + styleUrl: './add.component.css' +}) +export class AddComponent { + private router = inject(Router); + private readonly contactService: ContactsService; + public contactForm: FormGroup; + + constructor( + private formBuilder: FormBuilder, + private readonly service: ContactsService + ) { + this.contactForm = this.formBuilder.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + street: ['', Validators.required], + city: ['', Validators.required], + }); + this.contactService = service; + } + cancel(): void { + this.router.navigate(['/contacts']); + } + addContact(): void { + if (this.contactForm.valid) { + const newContact: Contact = { + firstName: this.contactForm.value.firstName, + lastName: this.contactForm.value.lastName, + street: this.contactForm.value.street, + city: this.contactForm.value.city, + }; + + this.contactService.addContact(newContact).subscribe({ + next: (response) => { + console.log('Contact added successfully:', response); + this.contactForm.reset(); + this.router.navigate(['/contacts']); + }, + error: (err) => { + console.error('Failed to add contact:', err); + }, + }); + } + } +} + diff --git a/src/app/contacts/contacts.module.ts b/src/app/contacts/contacts.module.ts new file mode 100644 index 00000000..e8be1a69 --- /dev/null +++ b/src/app/contacts/contacts.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AddComponent } from './add/add.component'; +import { ViewComponent } from './view/view.component'; +import { ListComponent } from './list/list.component'; +import { EditComponent } from './edit/edit.component'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; + +@NgModule({ + declarations: [AddComponent, ViewComponent, ListComponent, EditComponent], + imports: [CommonModule, ReactiveFormsModule, RouterModule], + exports: [AddComponent, ViewComponent, ListComponent], +}) +export class ContactsModule {} diff --git a/src/app/contacts/contacts.service.spec.ts b/src/app/contacts/contacts.service.spec.ts new file mode 100644 index 00000000..b64b5962 --- /dev/null +++ b/src/app/contacts/contacts.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ContactsService } from './contacts.service'; + +describe('ContactsService', () => { + let service: ContactsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ContactsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/contacts.service.ts b/src/app/contacts/contacts.service.ts new file mode 100644 index 00000000..84ad0a92 --- /dev/null +++ b/src/app/contacts/contacts.service.ts @@ -0,0 +1,30 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Contact } from './models/contact'; +import { CONTACTS } from '../data/contacts'; +import { environment } from '../environment/environment'; +import { Observable, lastValueFrom } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class ContactsService { + private http = inject(HttpClient); + + public getContactById(id: string): Observable { + return this.http.get(`${environment.api}/${id}`); + } + public getContacts(): Observable { + return this.http.get(`${environment.api}`); + } + + public addContact(contact: Contact): Observable { + return this.http.post(`${environment.api}`, contact); + } + public updateContact(c: Contact): Observable { + return this.http.put(`${environment.api}/${c.id}`, c); + } + public deleteContactById(id: string): Observable { + return this.http.delete(`${environment.api}/${id}`); + } +} diff --git a/src/app/contacts/edit/edit.component.css b/src/app/contacts/edit/edit.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/contacts/edit/edit.component.html b/src/app/contacts/edit/edit.component.html new file mode 100644 index 00000000..bc2d1952 --- /dev/null +++ b/src/app/contacts/edit/edit.component.html @@ -0,0 +1,75 @@ +
+

Edit Contact

+
+
+
+
+
+
+

Edit Contact

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+
+
+
+
+
+
diff --git a/src/app/contacts/edit/edit.component.spec.ts b/src/app/contacts/edit/edit.component.spec.ts new file mode 100644 index 00000000..6676ccfd --- /dev/null +++ b/src/app/contacts/edit/edit.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditComponent } from './edit.component'; + +describe('EditComponent', () => { + let component: EditComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [EditComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(EditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/edit/edit.component.ts b/src/app/contacts/edit/edit.component.ts new file mode 100644 index 00000000..2ca79e2d --- /dev/null +++ b/src/app/contacts/edit/edit.component.ts @@ -0,0 +1,75 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ContactsService } from '../contacts.service'; +import { Observable } from 'rxjs'; +import { Contact } from '../models/contact'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-edit', + standalone: true, + imports: [CommonModule, ReactiveFormsModule], + templateUrl: './edit.component.html', + styleUrl: './edit.component.css', +}) +export class EditComponent implements OnInit { + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); + private readonly formBuilder = inject(FormBuilder); + private readonly contactsService = inject(ContactsService); + + public contactForm: FormGroup; + public contact$!: Observable; + public id: string | null = null; + + constructor() { + this.contactForm = this.formBuilder.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + street: ['', Validators.required], + city: ['', Validators.required], + }); + } + + ngOnInit(): void { + this.id = this.route.snapshot.paramMap.get('id'); + console.log('Editing contact with id:', this.id); + + if (this.id) { + this.contact$ = this.contactsService.getContactById(this.id); + this.contact$.subscribe((contact) => { + if (contact) { + this.contactForm.patchValue({ + firstName: contact.firstName, + lastName: contact.lastName, + street: contact.street, + city: contact.city + }); + console.log('found:', contact); + console.log('found$', this.contact$); + } else { + console.error('Contact not found with id:', this.id); + } + }); + } + } + cancel(): void { + this.router.navigate(['/contacts']); + } + updateContact(): void { + if (this.contactForm.valid && this.id) { + const updatedContact: Contact = { + id: this.id, + firstName: this.contactForm.value.firstName, + lastName: this.contactForm.value.lastName, + street: this.contactForm.value.street, + city: this.contactForm.value.city, + }; + + this.contactsService.updateContact(updatedContact).subscribe(() => { + this.router.navigate(['/contacts']); + }); + } + } +} diff --git a/src/app/contacts/list/list.component.css b/src/app/contacts/list/list.component.css new file mode 100644 index 00000000..84f87310 --- /dev/null +++ b/src/app/contacts/list/list.component.css @@ -0,0 +1,6 @@ +.red { + color: red; +} +#item1 { + color: green; +} diff --git a/src/app/contacts/list/list.component.html b/src/app/contacts/list/list.component.html new file mode 100644 index 00000000..c44f6494 --- /dev/null +++ b/src/app/contacts/list/list.component.html @@ -0,0 +1,44 @@ +
+

Contacts List

+
+ + + + + + + + + + + + + + + + + + + + + + +
IdFirst NameLast NameStreetCity
+ {{ c.id }} + + + {{ c.firstName }} + {{ c.street }}{{ c.city}}{{ c.city }} + ✂️ + 🗑️ +
diff --git a/src/app/contacts/list/list.component.spec.ts b/src/app/contacts/list/list.component.spec.ts new file mode 100644 index 00000000..b602c867 --- /dev/null +++ b/src/app/contacts/list/list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ListComponent } from './list.component'; + +describe('ListComponent', () => { + let component: ListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/list/list.component.ts b/src/app/contacts/list/list.component.ts new file mode 100644 index 00000000..719cb7c9 --- /dev/null +++ b/src/app/contacts/list/list.component.ts @@ -0,0 +1,48 @@ +import { Component, inject } from '@angular/core'; +import { Router, RouterModule } from '@angular/router'; +import { Observable } from 'rxjs/internal/Observable'; +import { Contact } from '../models/contact'; +import { ContactsService } from '../contacts.service'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-list', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './list.component.html', + styleUrl: './list.component.css' +}) +export class ListComponent { + private readonly router = inject(Router); + + public contacts$ = new Observable(); + + constructor(private readonly contactsService: ContactsService) {} + + ngOnInit(): void { + this.contacts$ = this.contactsService.getContacts(); + this.contacts$.subscribe((contacts) => { + contacts.forEach((contact) => { + console.log(contact.firstName + ' ' + contact.lastName); + }); + }); + } + refreshContacts(): void { + this.contacts$ = this.contactsService.getContacts(); + } + deleteContact(id: string): void { + const confirmed = confirm('Are you sure you want to delete this contact?'); + if (confirmed) { + this.contactsService.deleteContactById(id).subscribe({ + next: () => { + console.log(`Contact with ID ${id} deleted successfully.`); + //this.router.navigate(['/contacts']); // this doesn't work as we are already on /contacts so we'll call a refresh method instead + this.refreshContacts(); + }, + error: (err) => { + console.error('Delete failed:', err); + }, + }); + } + } +} diff --git a/src/app/contacts/models/contact.ts b/src/app/contacts/models/contact.ts new file mode 100644 index 00000000..95d0cd5a --- /dev/null +++ b/src/app/contacts/models/contact.ts @@ -0,0 +1,7 @@ +export interface Contact { + id?: string | null; + firstName: string; + lastName: string + street: string; + city: string; +} diff --git a/src/app/contacts/view/view.component.css b/src/app/contacts/view/view.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/contacts/view/view.component.html b/src/app/contacts/view/view.component.html new file mode 100644 index 00000000..aa9a5ac3 --- /dev/null +++ b/src/app/contacts/view/view.component.html @@ -0,0 +1,10 @@ +
+

A Contact

+
+
+

+ {{ contact.firstName }} +

+

Street:{{ contact.street }}

+

{{ contact.city }}

+
diff --git a/src/app/contacts/view/view.component.spec.ts b/src/app/contacts/view/view.component.spec.ts new file mode 100644 index 00000000..380ab164 --- /dev/null +++ b/src/app/contacts/view/view.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ViewComponent } from './view.component'; + +describe('ViewComponent', () => { + let component: ViewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ViewComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/contacts/view/view.component.ts b/src/app/contacts/view/view.component.ts new file mode 100644 index 00000000..49d51413 --- /dev/null +++ b/src/app/contacts/view/view.component.ts @@ -0,0 +1,22 @@ +import { Component, inject } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Contact } from '../models/contact'; +import { ActivatedRoute } from '@angular/router'; +import { ContactsService } from '../contacts.service'; +import { CommonModule, NgIf } from '@angular/common'; + +@Component({ + selector: 'app-view', + standalone: true, + imports: [CommonModule, NgIf], + templateUrl: './view.component.html', + styleUrl: './view.component.css' +}) +export class ViewComponent { + contact$ = new Observable(); + route = inject(ActivatedRoute); + id: string | null = this.route.snapshot.paramMap.get('id'); + constructor(private readonly contactsService: ContactsService) { + this.contact$ = this.contactsService.getContactById(this.id as string); + } +} diff --git a/src/app/data/contacts.json b/src/app/data/contacts.json new file mode 100644 index 00000000..bca57c14 --- /dev/null +++ b/src/app/data/contacts.json @@ -0,0 +1,32 @@ +{ + "contacts": [ + { + "id": "1", + "firstName": "Lola", + "lastName": "Pug", + "street": "Small Dog Road", + "city": "Haribo" + }, + { + "id": "2", + "firstName": "Red", + "lastName": "Cat", + "street": "Black and White lane", + "city": "Appricot" + }, + { + "id": "3", + "firstName": "Bella", + "lastName": "Cat", + "street": "Fluffy black blvd", + "city": "Geronimo" + }, + { + "id": "6f0e", + "firstName": "fa", + "lastName": "faa", + "street": "faa", + "city": "fa" + } + ] +} \ No newline at end of file diff --git a/src/app/data/contacts.ts b/src/app/data/contacts.ts new file mode 100644 index 00000000..e96c46eb --- /dev/null +++ b/src/app/data/contacts.ts @@ -0,0 +1,25 @@ +import { Contact } from '../contacts/models/contact'; + +export const CONTACTS: Contact[] = [ + { + id: '1', + firstName: 'Lola', + lastName: 'Pug', + street: 'Small Dog Road', + city: 'Haribo' + }, + { + id: '2', + firstName: 'Red', + lastName: 'Cat', + street: 'Black and White lane', + city: 'Appricot' + }, + { + id: '3', + firstName: 'Bella', + lastName: 'Cat', + street: 'Fluffy black blvd', + city: 'Geronimo' + }, +]; \ No newline at end of file diff --git a/src/app/environment/environment.ts b/src/app/environment/environment.ts new file mode 100644 index 00000000..1eb966eb --- /dev/null +++ b/src/app/environment/environment.ts @@ -0,0 +1,3 @@ +export const environment = { + api: 'http://localhost:3000/contacts', +}; diff --git a/src/app/layout/menu/menu.component.css b/src/app/layout/menu/menu.component.css index 75955b3c..e69de29b 100644 --- a/src/app/layout/menu/menu.component.css +++ b/src/app/layout/menu/menu.component.css @@ -1,10 +0,0 @@ -:host { - padding: 0 0.5em; -} - -ul { - padding: 0; -} -li { - list-style: none; -} diff --git a/src/app/layout/menu/menu.component.html b/src/app/layout/menu/menu.component.html index 7c5ec7a2..923d3401 100644 --- a/src/app/layout/menu/menu.component.html +++ b/src/app/layout/menu/menu.component.html @@ -1,5 +1,27 @@ -

Menu

- + diff --git a/src/app/layout/menu/menu.component.specs.ts b/src/app/layout/menu/menu.component.specs.ts new file mode 100644 index 00000000..8b5a378e --- /dev/null +++ b/src/app/layout/menu/menu.component.specs.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MenuComponent } from './menu.component'; + +describe('MenuComponent', () => { + let component: MenuComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [MenuComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +});