Skip to content

Commit ec2f14d

Browse files
authored
Merge pull request #6 from actionanand/features/1-http-feature
Features/1 http feature
2 parents 2f7b6a5 + a1237c8 commit ec2f14d

19 files changed

+247
-29
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"scripts": {
55
"ng": "ng",
66
"start": "ng serve",
7-
"develop": "ng serve --proxy-config proxy.json",
7+
"develop": "ng serve --port 5600 --proxy-config proxy.conf.js",
88
"build": "ng build",
99
"watch": "ng build --watch --configuration development",
1010
"build:gh": "ng build --base-href /angular-http-project/",

proxy.conf.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// var defaultTarget = 'http://localhost:3000';
2+
var defaultTarget = 'https://3000-actionanand-angularhttp-six5y8k89a8.ws-us116.gitpod.io';
3+
4+
module.exports = [
5+
{
6+
context: ['/api/v2/*'],
7+
target: defaultTarget,
8+
secure: false,
9+
logLevel: 'debug',
10+
changeOrigin: true,
11+
pathRewrite: {
12+
'^/api/v2/': '/',
13+
},
14+
},
15+
{
16+
context: ['/api/v2/**'],
17+
target: defaultTarget,
18+
secure: false,
19+
logLevel: 'debug',
20+
changeOrigin: true,
21+
pathRewrite: {
22+
'^/api/v2/': '/',
23+
},
24+
},
25+
];

proxy.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
{
22
"/api/v2/*": {
3-
"target": "https://3000-actionanand-angularhttp-six5y8k89a8.ws-us116.gitpod.io",
3+
"target": "http://localhost:3000",
44
"secure": false,
55
"logLevel": "debug",
66
"changeOrigin": true,
77
"pathRewrite": {
88
"^/api/v2/": "/"
99
}
1010
},
11-
"/api/v1/*": {
11+
"/api/v2/**": {
1212
"target": "http://localhost:3000",
1313
"secure": false,
1414
"logLevel": "debug",
1515
"changeOrigin": true,
1616
"pathRewrite": {
17-
"^/api/v1/": "/"
17+
"^/api/v2/": "/"
1818
}
1919
}
2020
}

src/app/app.component.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
@if (error()) {
2+
<app-error-modal title="An Error has occurred!" [message]="error()" />
3+
}
4+
15
<header>
26
<img src="logo.png" alt="Stylized globe" />
37
<h1>PlacePicker</h1>
4-
<p>
5-
Create your personal collection of places you would like to visit or you
6-
have visited.
7-
</p>
8+
<p>Create your personal collection of places you would like to visit or you have visited.</p>
89
</header>
910

1011
<main>

src/app/app.component.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { Component } from '@angular/core';
1+
import { Component, inject } from '@angular/core';
22

33
import { AvailablePlacesComponent } from './places/available-places/available-places.component';
44
import { UserPlacesComponent } from './places/user-places/user-places.component';
5+
import { ErrorService } from './services/error.service';
6+
import { ErrorModalComponent } from './shared/modal/error-modal/error-modal.component';
57

68
@Component({
79
selector: 'app-root',
810
standalone: true,
911
templateUrl: './app.component.html',
1012
styleUrl: './app.component.css',
11-
imports: [AvailablePlacesComponent, UserPlacesComponent],
13+
imports: [AvailablePlacesComponent, UserPlacesComponent, ErrorModalComponent],
1214
})
1315
export class AppComponent {
14-
styles = ['color: indigo', 'background: #90EE90', 'font-weight: bold', 'font-size: 18px'].join(';');
16+
private styles = ['color: indigo', 'background: #90EE90', 'font-weight: bold', 'font-size: 18px'].join(';');
17+
private errorServ = inject(ErrorService);
18+
protected error = this.errorServ.error;
1519

1620
constructor() {
1721
console.log(

src/app/places/available-places/available-places.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class AvailablePlacesComponent implements OnInit {
4141
}
4242

4343
onSelectPlaces(selectedPlace: Place) {
44-
const selectPlaceSub = this.placeServ.addPlaceToUserPlaces(selectedPlace.id).subscribe({
44+
const selectPlaceSub = this.placeServ.addPlaceToUserPlaces(selectedPlace).subscribe({
4545
next: resp => console.log('Place added. ', resp),
4646
});
4747

src/app/places/user-places/user-places.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
}
99

1010
@if (places()) {
11-
<app-places [places]="places()!" />
12-
} @else if (places()?.length === 0) {
11+
<app-places [places]="places()!" (selectPlace)="onSelectPlaceRemove($event)" />
12+
} @else if (places().length === 0) {
1313
<p class="fallback-text">Unfortunately, no places could be found.</p>
1414
}
1515
</app-places-container>

src/app/places/user-places/user-places.component.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Component, DestroyRef, inject, OnInit, signal } from '@angular/core';
22

33
import { PlacesContainerComponent } from '../places-container/places-container.component';
44
import { PlacesComponent } from '../places.component';
5-
import { Place } from '../../models/place.model';
65
import { PlacesService } from '../../services/places.service';
6+
import { Place } from '../../models/place.model';
77

88
@Component({
99
selector: 'app-user-places',
@@ -13,21 +13,18 @@ import { PlacesService } from '../../services/places.service';
1313
imports: [PlacesContainerComponent, PlacesComponent],
1414
})
1515
export class UserPlacesComponent implements OnInit {
16-
places = signal<Place[] | undefined>(undefined);
1716
isFetching = signal(false);
1817
errorMsg = signal('');
1918

2019
private placeServ = inject(PlacesService);
2120
private destroyRef = inject(DestroyRef);
2221

22+
places = this.placeServ.loadedUserPlaces;
23+
2324
ngOnInit(): void {
2425
this.isFetching.set(true);
2526

2627
const availablePlaceSub = this.placeServ.loadUserPlaces().subscribe({
27-
next: resp => {
28-
this.places.set(resp?.places);
29-
this.errorMsg.set('');
30-
},
3128
complete: () => {
3229
this.isFetching.set(false);
3330
},
@@ -39,4 +36,12 @@ export class UserPlacesComponent implements OnInit {
3936

4037
this.destroyRef.onDestroy(() => availablePlaceSub.unsubscribe());
4138
}
39+
40+
onSelectPlaceRemove(place: Place) {
41+
const removePlSub = this.placeServ.removeUserPlace(place).subscribe({
42+
next: resp => console.log('Place renoved! ', resp),
43+
});
44+
45+
this.destroyRef.onDestroy(() => removePlSub.unsubscribe());
46+
}
4247
}

src/app/services/error.service.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Injectable, signal } from '@angular/core';
2+
3+
@Injectable({
4+
providedIn: 'root',
5+
})
6+
export class ErrorService {
7+
private _error = signal('');
8+
9+
error = this._error.asReadonly();
10+
11+
showError(message: string) {
12+
console.error(message);
13+
this._error.set(message);
14+
}
15+
16+
clearError() {
17+
this._error.set('');
18+
}
19+
}

src/app/services/places.service.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { catchError, map, tap } from 'rxjs/operators';
66
import { throwError } from 'rxjs';
77

88
import { Place } from '../models/place.model';
9+
import { ErrorService } from './error.service';
910

1011
@Injectable({
1112
providedIn: 'root',
@@ -15,22 +16,61 @@ export class PlacesService {
1516
loadedUserPlaces = this.userPlaces.asReadonly();
1617

1718
private http = inject(HttpClient);
19+
private errorServ = inject(ErrorService);
1820

1921
loadAvailablePlaces() {
2022
return this.fetchPlaces('/api/v2/places', 'Error loading available places!');
2123
}
2224

2325
loadUserPlaces() {
24-
return this.fetchPlaces('/api/v2/user-places', 'Error loading user places!');
26+
return this.fetchPlaces('/api/v2/user-places', 'Error loading user places!').pipe(
27+
tap({
28+
next: resp => {
29+
if (resp) {
30+
this.userPlaces.set(resp.places);
31+
}
32+
},
33+
}),
34+
);
2535
}
2636

27-
addPlaceToUserPlaces(placeId: string) {
28-
return this.http.put('/api/v2/user-places', {
29-
placeId,
30-
});
37+
addPlaceToUserPlaces(place: Place) {
38+
const prevPlaces = this.userPlaces();
39+
40+
if (!prevPlaces.some(p => p.id === place.id)) {
41+
// optimistic update
42+
this.userPlaces.set([...prevPlaces, place]);
43+
}
44+
45+
return this.http
46+
.put('/api/v2/user-places', {
47+
placeId: place.id,
48+
})
49+
.pipe(
50+
catchError(err => {
51+
this.userPlaces.set(prevPlaces);
52+
this.errorServ.showError('Unable to store the selected place!');
53+
return throwError(() => new Error('Unable to store the selected place!'));
54+
}),
55+
);
3156
}
3257

33-
removeUserPlace(place: Place) {}
58+
removeUserPlace(place: Place) {
59+
const prevPlaces = this.userPlaces();
60+
61+
if (prevPlaces.some(p => p.id === place.id)) {
62+
// optimistic update
63+
this.userPlaces.set(prevPlaces.filter(pl => pl.id !== place.id));
64+
}
65+
66+
return this.http.delete('/api/v2/user-places/' + place.id).pipe(
67+
catchError(err => {
68+
this.userPlaces.set(prevPlaces);
69+
this.errorServ.showError('Unable to remove the selected place!');
70+
return throwError(() => new Error('Unable to remove the selected place!'));
71+
}),
72+
);
73+
}
3474

3575
private fetchPlaces(url: string, errMsg: string) {
3676
return this.http

0 commit comments

Comments
 (0)