Skip to content
Open
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
providers: [provideRouter(routes), provideHttpClient()],
};
8 changes: 8 additions & 0 deletions src/app/components/add-pet-form/add-pet-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { ModalService } from '../../shared/services/modal.service';
import { CommonModule } from '@angular/common';
import { PetsService } from '../../shared/services/pets.service';
import { pipe } from 'rxjs';
import { Pet } from '../../../data/pets';

@Component({
selector: 'app-add-pet-form',
Expand All @@ -13,6 +16,7 @@ import { CommonModule } from '@angular/common';
export class AddPetFormComponent {
private fb = inject(FormBuilder);
private modalService = inject(ModalService);
private petsService = inject(PetsService);

petForm = this.fb.group({
name: ['', Validators.required],
Expand All @@ -24,6 +28,10 @@ export class AddPetFormComponent {
handleSubmit() {
if (this.petForm.valid) {
console.log('Submitted pet:', this.petForm.value);
const pet = this.petForm.value;
// this.petsService.addPet(pet).subscribe((savedPet) => {
// this.modalService.close();
// });
this.modalService.close();
} else {
console.log('Form is invalid');
Expand Down
5 changes: 3 additions & 2 deletions src/app/components/pets-list/pets-list.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Component, Input } from '@angular/core';
import { Component, effect, inject, Input, signal } from '@angular/core';
import { Pet } from '../../../data/pets';
import { PetCardComponent } from '../pet-card/pet-card.component';
import { PetsService } from '../../shared/services/pets.service';

@Component({
selector: 'app-pets-list',
standalone: true,
imports: [PetCardComponent],
templateUrl: './pets-list.component.html',
styleUrl: './pets-list.component.css'
styleUrl: './pets-list.component.css',
})
export class PetsListComponent {
@Input() pets: Pet[] = [];
Expand Down
18 changes: 12 additions & 6 deletions src/app/pages/pet-details/pet-details.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@
<div
class="border border-black rounded-md w-[70%] h-[70%] overflow-hidden flex flex-col md:flex-row p-5"
>
@if(pet()){
<div class="h-full w-full md:w-[35%]">
<img
[src]="pet?.image"
[alt]="pet?.name"
[src]="pet()?.image"
[alt]="pet()?.name"
class="object-contain w-full h-full"
/>
</div>
<div class="w-full md:w-[65%] h-full pt-[30px] flex flex-col p-3">
<h1>Name: {{ pet?.name }}</h1>
<h1>Type: {{ pet?.type }}</h1>
<h1>Adopted: {{ pet?.adopted ? "yes" : "no" }}</h1>
<h1>Name: {{ pet()?.name }}</h1>
<h1>Type: {{ pet()?.type }}</h1>
<h1>Adopted: {{ pet()?.adopted ? "yes" : "no" }}</h1>

@if (!pet?.adopted) {
@if (!pet()?.adopted) {
<button
class="w-[70px] border border-black rounded-md hover:bg-green-400 my-5"
>
Expand All @@ -26,5 +27,10 @@ <h1>Adopted: {{ pet?.adopted ? "yes" : "no" }}</h1>
Delete
</button>
</div>
} @else {
<div class="h-full w-full text-center">
<p class="">Pet not found. It may have been removed or never existed.</p>
</div>
}
</div>
</div>
31 changes: 16 additions & 15 deletions src/app/pages/pet-details/pet-details.component.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Pet, pets } from '../../../data/pets';
import { Component, computed, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { PetsService } from '../../shared/services/pets.service';
import { map, switchMap } from 'rxjs';

@Component({
selector: 'app-pet-details',
standalone: true,
imports: [],
templateUrl: './pet-details.component.html',
styleUrl: './pet-details.component.css'
styleUrl: './pet-details.component.css',
})
export class PetDetailsComponent {
pet: Pet | null = null;
pets = pets;
private petsService = inject(PetsService);
private route = inject(ActivatedRoute);

constructor(private route: ActivatedRoute, private router: Router) {
const id = Number(this.route.snapshot.paramMap.get('id'));
const foundPet = pets.find((p) => p.id === id);
readonly id = computed(() => Number(this.route.snapshot.paramMap.get('id')));

if (!foundPet) {
this.router.navigate(['/pets']);
} else {
this.pet = foundPet;
}
}
readonly pet = toSignal(
this.route.paramMap.pipe(
map((params) => Number(params.get('id'))),
switchMap((id) => this.petsService.getPet(id))
),
{ initialValue: undefined }
);
}
2 changes: 1 addition & 1 deletion src/app/pages/pets/pets.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="flex flex-col justify-center items-center">
<app-pets-header (search)="setQuery($event)"></app-pets-header>
<app-pets-list [pets]="filteredPets"></app-pets-list>
<app-pets-list [pets]="filteredPets()"></app-pets-list>
</div>
22 changes: 12 additions & 10 deletions src/app/pages/pets/pets.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Component } from '@angular/core';
import { Component, computed, effect, inject, signal } from '@angular/core';
import { PetsHeaderComponent } from '../../components/pets-header/pets-header.component';
import { PetsListComponent } from '../../components/pets-list/pets-list.component';
import { pets } from '../../../data/pets';
import { PetsService } from '../../shared/services/pets.service';
import { toSignal } from '@angular/core/rxjs-interop';

@Component({
selector: 'app-pets',
Expand All @@ -11,16 +12,17 @@ import { pets } from '../../../data/pets';
styleUrl: './pets.component.css',
})
export class PetsComponent {
query = '';
allPets = pets;
readonly query = signal('');
private petsService = inject(PetsService);

readonly allPets = toSignal(this.petsService.getPets(), { initialValue: [] });

setQuery(query: string) {
this.query = query;
this.query.set(query);
}

get filteredPets() {
return this.allPets.filter((pet) =>
pet.name.toLowerCase().includes(this.query.toLowerCase())
);
}
readonly filteredPets = computed(() => {
const q = this.query().toLowerCase();
return this.allPets().filter((pet) => pet.name.toLowerCase().includes(q));
});
}
16 changes: 16 additions & 0 deletions src/app/shared/services/base.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { BaseService } from './base.service';

describe('BaseService', () => {
let service: BaseService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(BaseService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
39 changes: 39 additions & 0 deletions src/app/shared/services/base.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class BaseService {
// ✅ Angular 17 way: use inject() instead of constructor injection
// constructor(private readonly _http: HttpClient) {}
private readonly _http = inject(HttpClient);

/**
* Generic GET method to fetch data
*/
get<T>(url: string, params?: HttpParams, headers?: HttpHeaders) {
return this._http.get<T>(url, { params, headers });
}

/**
* Generic POST method to add new data
*/
post<T>(url: string, body: T, headers?: HttpHeaders) {
return this._http.post<T>(url, body, { headers });
}

/**
* Generic PUT method to update data
*/
put<T>(url: string, body: T, headers?: HttpHeaders) {
return this._http.put<T>(url, body, { headers });
}

/**
* Generic DELETE method to remove data
*/
delete<T>(url: string, params?: HttpParams, headers?: HttpHeaders) {
return this._http.delete<T>(url, { params, headers });
}
}
16 changes: 16 additions & 0 deletions src/app/shared/services/pets.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { PetsService } from './pets.service';

describe('PetsService', () => {
let service: PetsService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PetsService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
38 changes: 38 additions & 0 deletions src/app/shared/services/pets.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { catchError, Observable, of } from 'rxjs';
import { Pet } from '../../../data/pets';
import { BaseService } from './base.service';

@Injectable({
providedIn: 'root',
})
export class PetsService extends BaseService {
private apiUrl = 'https://pets-react-query-backend.eapi.joincoded.com/pets';

getPets(): Observable<Pet[]> {
return this.get<Pet[]>(this.apiUrl).pipe(
catchError((error) => {
console.error('Error fetching pets:', error);
return of([]);
})
);
}

getPet(id: number): Observable<Pet> {
return this.get<Pet>(this.apiUrl + '/' + id).pipe(
catchError((error) => {
console.error('Error fetching pet:', error);
return of();
})
);
}

addPet(pet: Pet): Observable<Pet> {
return this.post(this.apiUrl, pet).pipe(
catchError((error) => {
console.error('Error adding pet:', error);
return of();
})
);
}
}