diff --git a/angular.json b/angular.json
index 7a4f1ef7..5a06c996 100644
--- a/angular.json
+++ b/angular.json
@@ -94,5 +94,8 @@
}
}
}
+ },
+ "cli": {
+ "analytics": "594a010b-3dfd-4a42-8417-ac6fe54a56d8"
}
}
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 02972627..372a397a 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/:id', component: ViewComponent },
+ { path: 'contacts/add', component: AddComponent },
+ { path: 'test', component: AddComponent },
+ { 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..d20b6499 100644
--- a/src/app/app.component.css
+++ b/src/app/app.component.css
@@ -1,9 +1,18 @@
-:host {
- display: flex;
+
+body, :host {
+ background: #f4f6fb;
+ min-height: 100vh;
+ font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
+ margin: 0;
+ padding: 0;
}
.page {
- flex: 1;
- display: flex;
- justify-content: center;
+ max-width: 700px;
+ margin: 2em auto;
+ background: #fff;
+ border-radius: 12px;
+ box-shadow: 0 4px 24px rgba(0,0,0,0.08);
+ padding: 2em 2em 2em 2em;
+ min-height: 60vh;
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 8207184c..192ce8fe 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -4,10 +4,19 @@ import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LayoutModule } from './layout/layout.module';
+import { CommonModule } from '@angular/common';
+import { ContactsModule } from './contacts/contacts.module';
@NgModule({
declarations: [AppComponent],
- imports: [BrowserModule, AppRoutingModule, LayoutModule],
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ LayoutModule,
+ CommonModule,
+ ContactsModule
+ ],
+ providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
diff --git a/src/app/contacts/add/add.component.css b/src/app/contacts/add/add.component.css
new file mode 100644
index 00000000..25be94ac
--- /dev/null
+++ b/src/app/contacts/add/add.component.css
@@ -0,0 +1,47 @@
+
+
+form div {
+ margin-bottom: 1.2em;
+}
+
+label {
+ display: block;
+ margin-bottom: 0.4em;
+ font-weight: 400;
+ color: #333;
+}
+
+input[type="text"] {
+ width: 100%;
+ padding: 0.6em;
+ border-radius: 4px;
+ font-size: 1em;
+ transition: border-color 0.2s;
+ border: 1.5px solid #ccc;
+}
+
+input[type="text"]:focus {
+ border-color: #007bff;
+ outline: none;
+}
+
+button[type="submit"] {
+ background: #007bff;
+ border: none;
+ padding: 0.7em 1.5em;
+ border-radius: 4px;
+ font-size: 1em;
+ cursor: pointer;
+ transition: background 0.2s;
+ color: #fff;
+}
+
+button[type="submit"]:hover {
+ background: #0056b3;
+}
+
+h2 {
+ text-align: center;
+ color: #007bff;
+ margin-bottom: 1.5em;
+}
diff --git a/src/app/contacts/add/add.component.html b/src/app/contacts/add/add.component.html
new file mode 100644
index 00000000..58c76731
--- /dev/null
+++ b/src/app/contacts/add/add.component.html
@@ -0,0 +1,24 @@
+
\ No newline at end of file
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..bbfc724c
--- /dev/null
+++ b/src/app/contacts/add/add.component.ts
@@ -0,0 +1,39 @@
+import { Component } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ContactsService } from '../contacts.service';
+import { Contact } from '../models/contacts';
+
+@Component({
+ selector: 'app-add',
+ templateUrl: './add.component.html',
+ styleUrls: ['./add.component.css']
+})
+export class AddComponent {
+ contactForm: FormGroup;
+ cservice: ContactsService
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private readonly contactService: ContactsService
+ ) {
+ this.contactForm = this.formBuilder.group({
+ firstname: ['', Validators.required],
+ lastname: ['', Validators.required],
+ street: ['', Validators.required],
+ city: ['', Validators.required]
+ });
+ this.cservice = this.contactService;
+ }
+
+ addContact(): void {
+ const newContact: Contact = {
+ id: 0,
+ firstname: this.contactForm.value.firstname,
+ lastname: this.contactForm.value.lastname,
+ street: this.contactForm.value.street,
+ city: this.contactForm.value.city
+ };
+ this.cservice.AddContact(newContact);
+ this.contactForm.reset();
+ }
+}
\ 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..b2b77cc1
--- /dev/null
+++ b/src/app/contacts/contacts.module.ts
@@ -0,0 +1,17 @@
+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 { ReactiveFormsModule } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+import { EditComponent } from './edit/edit.component';
+
+
+
+@NgModule({
+ declarations: [AddComponent, ViewComponent, ListComponent, EditComponent],
+ imports: [CommonModule, ReactiveFormsModule, RouterModule],
+ exports: [AddComponent, ViewComponent, ListComponent, EditComponent],
+})
+export class ContactsModule { }
diff --git a/src/app/contacts/contacts.service.ts b/src/app/contacts/contacts.service.ts
new file mode 100644
index 00000000..8176d7f6
--- /dev/null
+++ b/src/app/contacts/contacts.service.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { Contact } from './models/contacts';
+import { CONTACTS } from '../data/contacts';
+
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ContactsService {
+ public contacts: Contact[] = CONTACTS;
+
+ public AddContact(c: Contact) {
+ c.id = this.contacts.length + 1;
+ this.contacts.push(c);
+ }
+
+ public GetContactById(id: number | null) {
+ const contact = this.contacts.find((contact) => contact.id === id);
+ if (!contact) {
+ return null;
+ }
+ return contact;
+ }
+
+ public UpdateContact(updated: Contact) {
+ const idx = this.contacts.findIndex(c => c.id === updated.id);
+ if (idx !== -1) {
+ this.contacts[idx] = updated;
+ }
+ }
+}
\ 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..6deb583f
--- /dev/null
+++ b/src/app/contacts/edit/edit.component.css
@@ -0,0 +1,45 @@
+form div {
+ margin-bottom: 1.2em;
+}
+
+label {
+ display: block;
+ margin-bottom: 0.4em;
+ font-weight: 400;
+ color: #333;
+}
+
+input[type="text"] {
+ width: 100%;
+ padding: 0.6em;
+ border-radius: 4px;
+ font-size: 1em;
+ transition: border-color 0.2s;
+ border: 1.5px solid #ccc;
+}
+
+input[type="text"]:focus {
+ border-color: #007bff;
+ outline: none;
+}
+
+button[type="submit"] {
+ background: #007bff;
+ border: none;
+ padding: 0.7em 1.5em;
+ border-radius: 4px;
+ font-size: 1em;
+ cursor: pointer;
+ transition: background 0.2s;
+ color: #fff;
+}
+
+button[type="submit"]:hover {
+ background: #0056b3;
+}
+
+h2 {
+ text-align: center;
+ color: #007bff;
+ margin-bottom: 1.5em;
+}
diff --git a/src/app/contacts/edit/edit.component.html b/src/app/contacts/edit/edit.component.html
new file mode 100644
index 00000000..4504c552
--- /dev/null
+++ b/src/app/contacts/edit/edit.component.html
@@ -0,0 +1,24 @@
+
+
+ Contact not found!
+
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..559ea1d9
--- /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({
+ declarations: [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..b23f8988
--- /dev/null
+++ b/src/app/contacts/edit/edit.component.ts
@@ -0,0 +1,50 @@
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+import { ContactsService } from '../contacts.service';
+import { Contact } from '../models/contacts';
+
+@Component({
+ selector: 'app-edit',
+ templateUrl: './edit.component.html',
+ styleUrls: ['./edit.component.css']
+})
+export class EditComponent implements OnInit {
+ contactForm: FormGroup;
+ contactId: number;
+ contact: Contact | null = null;
+
+ 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(this.contact);
+ }
+ }
+
+ saveContact(): void {
+ if (this.contact) {
+ const updatedContact: Contact = {
+ ...this.contact,
+ ...this.contactForm.value
+ };
+ this.contactsService.UpdateContact(updatedContact);
+ 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..552e7bba
--- /dev/null
+++ b/src/app/contacts/list/list.component.css
@@ -0,0 +1,58 @@
+a.edit-btn {
+ background: #007bff;
+ color: #fff;
+ border: none;
+ border-radius: 4px;
+ padding: 0.4em 1.1em;
+ font-size: 0.95em;
+ font-weight: 500;
+ text-decoration: none;
+ transition: background 0.2s, color 0.2s, box-shadow 0.2s;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.04);
+ margin-left: 0.5em;
+ cursor: pointer;
+ display: inline-block;
+}
+
+a.edit-btn:hover {
+ background: #0056b3;
+ color: #fff;
+ box-shadow: 0 4px 16px rgba(0,0,0,0.10);
+}
+table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 2em 0;
+ background: #fff;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+th, td {
+ padding: 1em;
+ text-align: left;
+}
+
+thead {
+ background: #007bff;
+ color: #fff;
+}
+
+tr {
+ border-bottom: 1px solid #eee;
+}
+
+tr:last-child {
+ border-bottom: none;
+}
+
+a {
+ color: #007bff;
+ text-decoration: none;
+ font-weight: 500;
+}
+
+a:hover {
+ text-decoration: underline;
+}
diff --git a/src/app/contacts/list/list.component.html b/src/app/contacts/list/list.component.html
new file mode 100644
index 00000000..cbe9458e
--- /dev/null
+++ b/src/app/contacts/list/list.component.html
@@ -0,0 +1,26 @@
+
+ -
+ {{ c.firstname }} {{ c.lastname }}
+
+
+
+
diff --git a/src/app/contacts/list/list.component.ts b/src/app/contacts/list/list.component.ts
new file mode 100644
index 00000000..457bce81
--- /dev/null
+++ b/src/app/contacts/list/list.component.ts
@@ -0,0 +1,16 @@
+import { Component } from '@angular/core';
+import { Contact } from '../models/contacts';
+import { ContactsService } from '../contacts.service';
+
+@Component({
+ selector: 'app-list',
+ templateUrl: './list.component.html',
+ styleUrl: './list.component.css'
+})
+export class ListComponent {
+ contacts: Contact[] = [];
+
+ constructor(private readonly contactService: ContactsService) {
+ this.contacts = contactService.contacts;
+ }
+}
diff --git a/src/app/contacts/models/contacts.ts b/src/app/contacts/models/contacts.ts
new file mode 100644
index 00000000..51472b33
--- /dev/null
+++ b/src/app/contacts/models/contacts.ts
@@ -0,0 +1,7 @@
+export interface Contact {
+ id: number | 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..bf46ad9f
--- /dev/null
+++ b/src/app/contacts/view/view.component.css
@@ -0,0 +1,39 @@
+.contact-view {
+ max-width: 370px;
+ margin: 2.5em auto;
+ padding: 2em 1.5em 1.5em 1.5em;
+ background: #f9fafb;
+ border-radius: 14px;
+ box-shadow: 0 4px 24px rgba(0,0,0,0.06);
+ font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
+ text-align: left;
+}
+
+h2 {
+ color: #2563eb;
+ margin-bottom: 1em;
+ font-size: 1.35em;
+ font-weight: 700;
+ letter-spacing: 0.03em;
+}
+h3 {
+ color: #222;
+ margin: 0.7em 0;
+ font-size: 1.08em;
+ font-weight: 400;
+}
+button {
+ background: #2563eb;
+ color: #fff;
+ border: none;
+ border-radius: 4px;
+ padding: 0.5em 1.2em;
+ font-size: 1em;
+ font-family: inherit;
+ cursor: pointer;
+ margin-top: 1.5em;
+ transition: background 0.2s;
+}
+button:hover {
+ background: #1e40af;
+}
\ No newline at end of file
diff --git a/src/app/contacts/view/view.component.html b/src/app/contacts/view/view.component.html
new file mode 100644
index 00000000..708b1dff
--- /dev/null
+++ b/src/app/contacts/view/view.component.html
@@ -0,0 +1,8 @@
+
+
+
+ {{ contact.firstname }} {{ contact.lastname }}
+
+ Street: {{ contact.street }}
+ City: {{ 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..52a4a560
--- /dev/null
+++ b/src/app/contacts/view/view.component.ts
@@ -0,0 +1,28 @@
+import { Component } from '@angular/core';
+import { Contact } from '../models/contacts';
+import { ContactsService } from '../contacts.service';
+import { ActivatedRoute } from '@angular/router';
+import { Location } from '@angular/common';
+
+@Component({
+ selector: 'app-view',
+ templateUrl: './view.component.html',
+ styleUrl: './view.component.css'
+})
+export class ViewComponent {
+ contact : Contact | null = null;
+
+ constructor(
+ private readonly contactService: ContactsService,
+ private readonly route: ActivatedRoute,
+ private location: Location
+ ) {
+ this.contact = this.contactService.GetContactById(
+ Number(route.snapshot.paramMap.get('id'))
+ );
+ }
+
+ goBack(): void {
+ this.location.back();
+ }
+}
diff --git a/src/app/data/contacts.ts b/src/app/data/contacts.ts
new file mode 100644
index 00000000..07066e12
--- /dev/null
+++ b/src/app/data/contacts.ts
@@ -0,0 +1,25 @@
+import { Contact } from '../contacts/models/contacts';
+
+export const CONTACTS: Contact[] = [
+ {
+ id: 1,
+ firstname: 'Lola',
+ lastname: 'Smith',
+ street: '123 Dog St',
+ city: 'Dogtown',
+ },
+ {
+ id: 2,
+ firstname: 'Red',
+ lastname: 'Cat',
+ street: '456 Cat Ave',
+ city: 'Cat City',
+ },
+ {
+ id: 3,
+ firstname: 'Bella',
+ lastname: 'Johnson',
+ street: '789 Catnip Blvd',
+ city: 'Cat City',
+ },
+];
diff --git a/src/app/layout/menu/menu.component.css b/src/app/layout/menu/menu.component.css
index 75955b3c..f64c0959 100644
--- a/src/app/layout/menu/menu.component.css
+++ b/src/app/layout/menu/menu.component.css
@@ -1,10 +1,42 @@
-:host {
- padding: 0 0.5em;
+nav {
+ background: #f8f9fa;
+ border-bottom: 1px solid #e0e0e0;
+ padding: 0.5em 0;
+ margin-bottom: 2em;
}
ul {
+ display: flex;
+ justify-content: center;
+ gap: 2em;
padding: 0;
+ margin: 0;
}
+
li {
list-style: none;
}
+
+a {
+ color: #222;
+ text-decoration: none;
+ font-weight: 500;
+ font-size: 1.05em;
+ padding: 0.5em 1em;
+ border-radius: 4px;
+ transition: background 0.2s, color 0.2s;
+}
+
+a:hover {
+ background: #e0e0e0;
+ color: #007bff;
+}
+
+h2 {
+ color: #222;
+ text-align: center;
+ font-size: 1.2em;
+ font-weight: 600;
+ margin-bottom: 0.5em;
+ letter-spacing: 0.03em;
+}
\ 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..99a43daf 100644
--- a/src/app/layout/menu/menu.component.html
+++ b/src/app/layout/menu/menu.component.html
@@ -1,5 +1,7 @@
-Menu
-
+
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index c58dc05c..e893cb3e 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,5 +3,7 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
-platformBrowserDynamic().bootstrapModule(AppModule)
- .catch(err => console.error(err));
+platformBrowserDynamic().bootstrapModule(AppModule, {
+ ngZoneEventCoalescing: true
+})
+ .catch(err => console.error(err));
\ No newline at end of file