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..d9a92e00 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -1,7 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
-const routes: Routes = [];
+const routes: Routes = [
+ { path: '', redirectTo: '/contacts', pathMatch: 'full' },
+ {
+ path: 'contacts',
+ loadChildren: () => import('./contacts/contacts.module').then(m => m.ContactsModule)
+ }
+];
@NgModule({
imports: [RouterModule.forRoot(routes)],
diff --git a/src/app/contacts/add/add.component.css b/src/app/contacts/add/add.component.css
new file mode 100644
index 00000000..380085ff
--- /dev/null
+++ b/src/app/contacts/add/add.component.css
@@ -0,0 +1,25 @@
+:host {
+ display: flex;
+ box-sizing: border-box;
+ width: 40vw;
+ flex-direction: column;
+}
+
+form {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ gap: 0.5em;
+}
+
+label {
+ font-weight: bold;
+}
+
+.actions {
+ display: flex;
+}
+
+.spacer {
+ flex: 1;
+}
\ No newline at end of file
diff --git a/src/app/contacts/add/add.component.html b/src/app/contacts/add/add.component.html
new file mode 100644
index 00000000..8dbe55c4
--- /dev/null
+++ b/src/app/contacts/add/add.component.html
@@ -0,0 +1,19 @@
+
Create Contact
+
\ No newline at end of file
diff --git a/src/app/contacts/add/add.component.ts b/src/app/contacts/add/add.component.ts
new file mode 100644
index 00000000..eb95841a
--- /dev/null
+++ b/src/app/contacts/add/add.component.ts
@@ -0,0 +1,35 @@
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Router } from '@angular/router';
+import { ContactsService } from '../contacts.service';
+
+@Component({
+ selector: 'app-add',
+ templateUrl: './add.component.html',
+ styleUrls: ['./add.component.css']
+})
+export class AddComponent implements OnInit {
+ contactForm: FormGroup;
+
+ constructor(
+ private fb: FormBuilder,
+ private contactsService: ContactsService,
+ private router: Router
+ ) {
+ this.contactForm = this.fb.group({
+ firstName: ['', Validators.required],
+ lastName: ['', Validators.required],
+ street: ['', Validators.required],
+ city: ['', Validators.required]
+ });
+ }
+
+ ngOnInit(): void {}
+
+ onSubmit(): void {
+ if (this.contactForm.valid) {
+ this.contactsService.addContact(this.contactForm.value);
+ this.router.navigate(['/contacts']);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/contacts/contacts-routing.module.ts b/src/app/contacts/contacts-routing.module.ts
new file mode 100644
index 00000000..090a1901
--- /dev/null
+++ b/src/app/contacts/contacts-routing.module.ts
@@ -0,0 +1,19 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { ListComponent } from './list/list.component';
+import { AddComponent } from './add/add.component';
+import { ViewComponent } from './view/view.component';
+import { EditComponent } from './edit/edit.component';
+
+const routes: Routes = [
+ { path: '', component: ListComponent },
+ { path: 'add', component: AddComponent },
+ { path: ':id', component: ViewComponent },
+ { path: ':id/edit', component: EditComponent }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class ContactsRoutingModule { }
\ No newline at end of file
diff --git a/src/app/contacts/contacts.module.ts b/src/app/contacts/contacts.module.ts
new file mode 100644
index 00000000..cab160b8
--- /dev/null
+++ b/src/app/contacts/contacts.module.ts
@@ -0,0 +1,24 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ReactiveFormsModule } from '@angular/forms';
+
+import { ContactsRoutingModule } from './contacts-routing.module';
+import { ListComponent } from './list/list.component';
+import { AddComponent } from './add/add.component';
+import { ViewComponent } from './view/view.component';
+import { EditComponent } from './edit/edit.component';
+
+@NgModule({
+ declarations: [
+ ListComponent,
+ AddComponent,
+ ViewComponent,
+ EditComponent
+ ],
+ imports: [
+ CommonModule,
+ ReactiveFormsModule,
+ ContactsRoutingModule
+ ]
+})
+export class ContactsModule { }
\ No newline at end of file
diff --git a/src/app/contacts/contacts.service.ts b/src/app/contacts/contacts.service.ts
new file mode 100644
index 00000000..75dfac29
--- /dev/null
+++ b/src/app/contacts/contacts.service.ts
@@ -0,0 +1,46 @@
+import { Injectable } from '@angular/core';
+import { Contact } from './models/contact';
+import { contacts } from './data/contacts';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ContactsService {
+ private contacts: Contact[] = [...contacts];
+ private nextId = 2;
+
+ getContacts(): Contact[] {
+ return this.contacts;
+ }
+
+ getContactById(id: number): Contact | undefined {
+ return this.contacts.find(contact => contact.id === id);
+ }
+
+ addContact(contact: Omit): Contact {
+ const newContact: Contact = {
+ ...contact,
+ id: this.nextId++
+ };
+ this.contacts.push(newContact);
+ return newContact;
+ }
+
+ updateContact(id: number, contact: Omit): Contact | null {
+ const index = this.contacts.findIndex(c => c.id === id);
+ if (index !== -1) {
+ this.contacts[index] = { ...contact, id };
+ return this.contacts[index];
+ }
+ return null;
+ }
+
+ deleteContact(id: number): boolean {
+ const index = this.contacts.findIndex(c => c.id === id);
+ if (index !== -1) {
+ this.contacts.splice(index, 1);
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/app/contacts/data/contacts.ts b/src/app/contacts/data/contacts.ts
new file mode 100644
index 00000000..3d9eac92
--- /dev/null
+++ b/src/app/contacts/data/contacts.ts
@@ -0,0 +1,11 @@
+import { Contact } from '../models/contact';
+
+export const contacts: Contact[] = [
+ {
+ id: 1,
+ firstName: 'Joe',
+ lastName: 'Blogs',
+ street: '123 Main St',
+ city: 'London'
+ }
+];
\ No newline at end of file
diff --git a/src/app/contacts/edit/edit.component.css b/src/app/contacts/edit/edit.component.css
new file mode 100644
index 00000000..380085ff
--- /dev/null
+++ b/src/app/contacts/edit/edit.component.css
@@ -0,0 +1,25 @@
+:host {
+ display: flex;
+ box-sizing: border-box;
+ width: 40vw;
+ flex-direction: column;
+}
+
+form {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ gap: 0.5em;
+}
+
+label {
+ font-weight: bold;
+}
+
+.actions {
+ display: flex;
+}
+
+.spacer {
+ flex: 1;
+}
\ No newline at end of file
diff --git a/src/app/contacts/edit/edit.component.html b/src/app/contacts/edit/edit.component.html
new file mode 100644
index 00000000..e3fa1d57
--- /dev/null
+++ b/src/app/contacts/edit/edit.component.html
@@ -0,0 +1,25 @@
+
+
+ Contact not found
+ The contact you are trying to edit does not exist.
+
\ No newline at end of file
diff --git a/src/app/contacts/edit/edit.component.ts b/src/app/contacts/edit/edit.component.ts
new file mode 100644
index 00000000..1ea1d8d0
--- /dev/null
+++ b/src/app/contacts/edit/edit.component.ts
@@ -0,0 +1,52 @@
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Contact } from '../models/contact';
+import { ContactsService } from '../contacts.service';
+
+@Component({
+ selector: 'app-edit',
+ templateUrl: './edit.component.html',
+ styleUrls: ['./edit.component.css']
+})
+export class EditComponent implements OnInit {
+ contactForm: FormGroup;
+ contact: Contact | undefined;
+ contactId: number;
+
+ constructor(
+ private fb: FormBuilder,
+ private route: ActivatedRoute,
+ private router: Router,
+ private contactsService: ContactsService
+ ) {
+ this.contactForm = this.fb.group({
+ firstName: ['', Validators.required],
+ lastName: ['', Validators.required],
+ street: ['', Validators.required],
+ city: ['', Validators.required]
+ });
+ this.contactId = 0;
+ }
+
+ ngOnInit(): void {
+ this.contactId = Number(this.route.snapshot.paramMap.get('id'));
+ this.contact = this.contactsService.getContactById(this.contactId);
+
+ if (this.contact) {
+ this.contactForm.patchValue({
+ firstName: this.contact.firstName,
+ lastName: this.contact.lastName,
+ street: this.contact.street,
+ city: this.contact.city
+ });
+ }
+ }
+
+ onSubmit(): void {
+ if (this.contactForm.valid && this.contact) {
+ this.contactsService.updateContact(this.contactId, this.contactForm.value);
+ this.router.navigate(['/contacts', this.contactId]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/app/contacts/list/list.component.css b/src/app/contacts/list/list.component.css
new file mode 100644
index 00000000..01f87f24
--- /dev/null
+++ b/src/app/contacts/list/list.component.css
@@ -0,0 +1,11 @@
+:host {
+ padding: 0.5em;
+ width: 400px;
+}
+
+.contact {
+ display: flex;
+ justify-content: space-between;
+ border-bottom: 1px solid #ddd;
+ padding: 0.5em 0;
+}
\ No newline at end of file
diff --git a/src/app/contacts/list/list.component.html b/src/app/contacts/list/list.component.html
new file mode 100644
index 00000000..e4764ccd
--- /dev/null
+++ b/src/app/contacts/list/list.component.html
@@ -0,0 +1,10 @@
+Contacts
+ 0; else noContacts" class="contact-container">
+
+
+
+ No contacts yet
+
\ No newline at end of file
diff --git a/src/app/contacts/list/list.component.ts b/src/app/contacts/list/list.component.ts
new file mode 100644
index 00000000..ccfb0ee2
--- /dev/null
+++ b/src/app/contacts/list/list.component.ts
@@ -0,0 +1,18 @@
+import { Component, OnInit } from '@angular/core';
+import { Contact } from '../models/contact';
+import { ContactsService } from '../contacts.service';
+
+@Component({
+ selector: 'app-list',
+ templateUrl: './list.component.html',
+ styleUrls: ['./list.component.css']
+})
+export class ListComponent implements OnInit {
+ contacts: Contact[] = [];
+
+ constructor(private contactsService: ContactsService) { }
+
+ ngOnInit(): void {
+ this.contacts = this.contactsService.getContacts();
+ }
+}
\ No newline at end of file
diff --git a/src/app/contacts/models/contact.ts b/src/app/contacts/models/contact.ts
new file mode 100644
index 00000000..eecadba4
--- /dev/null
+++ b/src/app/contacts/models/contact.ts
@@ -0,0 +1,7 @@
+export interface Contact {
+ id: number;
+ firstName: string;
+ lastName: string;
+ street: string;
+ city: string;
+}
\ No newline at end of file
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..2fee28a4
--- /dev/null
+++ b/src/app/contacts/view/view.component.html
@@ -0,0 +1,8 @@
+
+ {{ contact.firstName }} {{ contact.lastName }}
+ {{ contact.street }}, {{ contact.city }}
+ Edit
+
+
+ Contact not found
+
\ No newline at end of file
diff --git a/src/app/contacts/view/view.component.ts b/src/app/contacts/view/view.component.ts
new file mode 100644
index 00000000..1341e30f
--- /dev/null
+++ b/src/app/contacts/view/view.component.ts
@@ -0,0 +1,23 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Contact } from '../models/contact';
+import { ContactsService } from '../contacts.service';
+
+@Component({
+ selector: 'app-view',
+ templateUrl: './view.component.html',
+ styleUrls: ['./view.component.css']
+})
+export class ViewComponent implements OnInit {
+ contact: Contact | undefined;
+
+ constructor(
+ private route: ActivatedRoute,
+ private contactsService: ContactsService
+ ) { }
+
+ ngOnInit(): void {
+ const id = Number(this.route.snapshot.paramMap.get('id'));
+ this.contact = this.contactsService.getContactById(id);
+ }
+}
\ No newline at end of file
diff --git a/src/app/layout/menu/menu.component.html b/src/app/layout/menu/menu.component.html
index 7c5ec7a2..aed3259d 100644
--- a/src/app/layout/menu/menu.component.html
+++ b/src/app/layout/menu/menu.component.html
@@ -1,5 +1,5 @@
Menu