"https"
This commit is contained in:
parent
21a6ff53d8
commit
cfc3c0a9e4
|
@ -2,35 +2,41 @@ import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
import { ComponentOverviewComponent } from './components/component-overview/component-overview.component';
|
|
||||||
import { HeroesComponent } from './components/heroes/heroes.component';
|
|
||||||
import { MessagesComponent } from './components/messages/messages.component';
|
|
||||||
import { HeroDetailComponent } from './components/hero-detail/hero-detail.component';
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
|
||||||
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
|
||||||
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
|
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
|
||||||
import { InMemoryDataService } from './in-memory-data.service';
|
import { InMemoryDataService } from './services/in-memory-data.service';
|
||||||
|
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
||||||
|
import { HeroDetailComponent } from './components/hero-detail/hero-detail.component';
|
||||||
|
import { HeroesComponent } from './components/heroes/heroes.component';
|
||||||
|
import { HeroSearchComponent } from './components/hero-search/hero-search.component';
|
||||||
|
import { MessagesComponent } from './components/messages/messages.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
|
||||||
AppComponent,
|
|
||||||
ComponentOverviewComponent,
|
|
||||||
HeroesComponent,
|
|
||||||
MessagesComponent,
|
|
||||||
HeroDetailComponent,
|
|
||||||
DashboardComponent
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
HttpClientModule,
|
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
|
HttpClientModule,
|
||||||
|
|
||||||
|
// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
|
||||||
|
// and returns simulated server responses.
|
||||||
|
// Remove it when a real server is ready to receive requests.
|
||||||
HttpClientInMemoryWebApiModule.forRoot(
|
HttpClientInMemoryWebApiModule.forRoot(
|
||||||
InMemoryDataService, { dataEncapsulation: false }
|
InMemoryDataService, { dataEncapsulation: false }
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
providers: [],
|
declarations: [
|
||||||
bootstrap: [AppComponent]
|
AppComponent,
|
||||||
|
DashboardComponent,
|
||||||
|
HeroesComponent,
|
||||||
|
HeroDetailComponent,
|
||||||
|
MessagesComponent,
|
||||||
|
HeroSearchComponent
|
||||||
|
],
|
||||||
|
bootstrap: [ AppComponent ]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
|
@ -1,4 +1,9 @@
|
||||||
<a *ngFor="let hero of heroes"
|
<h2>Top Heroes</h2>
|
||||||
routerLink="/detail/{{hero.id}}">
|
<div class="heroes-menu">
|
||||||
{{hero.name}}
|
<a *ngFor="let hero of heroes"
|
||||||
</a>
|
routerLink="/detail/{{hero.id}}">
|
||||||
|
{{hero.name}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-hero-search></app-hero-search>
|
|
@ -6,4 +6,5 @@
|
||||||
<input id="hero-name" [(ngModel)]="hero.name" placeholder="Hero name"/>
|
<input id="hero-name" [(ngModel)]="hero.name" placeholder="Hero name"/>
|
||||||
</div>
|
</div>
|
||||||
<button (click)="goBack()">go back</button>
|
<button (click)="goBack()">go back</button>
|
||||||
|
<button (click)="save()">save</button>
|
||||||
</div>
|
</div>
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { HeroService } from '../../services/hero.service';
|
||||||
styleUrls: [ './hero-detail.component.scss' ]
|
styleUrls: [ './hero-detail.component.scss' ]
|
||||||
})
|
})
|
||||||
export class HeroDetailComponent implements OnInit {
|
export class HeroDetailComponent implements OnInit {
|
||||||
@Input() hero?: Hero;
|
hero: Hero;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -24,12 +24,17 @@ export class HeroDetailComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
getHero(): void {
|
getHero(): void {
|
||||||
const id = Number(this.route.snapshot.paramMap.get('id'));
|
const id = this.route.snapshot.paramMap.get('id');
|
||||||
this.heroService.getHero(id)
|
this.heroService.getHero(Number(id))
|
||||||
.subscribe(hero => this.hero = hero);
|
.subscribe(hero => this.hero = hero);
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack(): void {
|
goBack(): void {
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
this.heroService.updateHero(this.hero)
|
||||||
|
.subscribe(() => this.goBack());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<div id="search-component">
|
||||||
|
<label for="search-box">Hero Search</label>
|
||||||
|
<input #searchBox id="search-box" (input)="search(searchBox.value)" />
|
||||||
|
|
||||||
|
<ul class="search-result">
|
||||||
|
<li *ngFor="let hero of heroes$ | async" >
|
||||||
|
<a routerLink="/detail/{{hero.id}}">
|
||||||
|
{{hero.name}}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* HeroSearch private styles */
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
padding: .5rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: #336699 auto 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
.search-result li a {
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
border-left: 1px solid gray;
|
||||||
|
border-right: 1px solid gray;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
padding: .5rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result li a:hover {
|
||||||
|
background-color: #435A60;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search-result {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HeroSearchComponent } from './hero-search.component';
|
||||||
|
|
||||||
|
describe('HeroSearchComponent', () => {
|
||||||
|
let component: HeroSearchComponent;
|
||||||
|
let fixture: ComponentFixture<HeroSearchComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ HeroSearchComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HeroSearchComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
|
||||||
|
import {
|
||||||
|
debounceTime, distinctUntilChanged, switchMap
|
||||||
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { Hero } from '../../interface/hero.interface';
|
||||||
|
import { HeroService } from '../../services/hero.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-hero-search',
|
||||||
|
templateUrl: './hero-search.component.html',
|
||||||
|
styleUrls: [ './hero-search.component.scss' ]
|
||||||
|
})
|
||||||
|
export class HeroSearchComponent implements OnInit {
|
||||||
|
heroes$: Observable<Hero[]>;
|
||||||
|
private searchTerms = new Subject<string>();
|
||||||
|
|
||||||
|
constructor(private heroService: HeroService) {}
|
||||||
|
|
||||||
|
// Push a search term into the observable stream.
|
||||||
|
search(term: string): void {
|
||||||
|
this.searchTerms.next(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.heroes$ = this.searchTerms.pipe(
|
||||||
|
// wait 300ms after each keystroke before considering the term
|
||||||
|
debounceTime(300),
|
||||||
|
|
||||||
|
// ignore new term if same as previous term
|
||||||
|
distinctUntilChanged(),
|
||||||
|
|
||||||
|
// switch to new search observable each time the term changes
|
||||||
|
switchMap((term: string) => this.heroService.searchHeroes(term)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,21 @@
|
||||||
|
<h2>My Heroes</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label id="new-hero">Hero name: </label>
|
||||||
|
<input for="new-hero" #heroName />
|
||||||
|
|
||||||
|
<!-- (click) passes input value to add() and then clears the input -->
|
||||||
|
<button class="add-button" (click)="add(heroName.value); heroName.value=''">
|
||||||
|
Add hero
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul class="heroes">
|
<ul class="heroes">
|
||||||
<li *ngFor="let hero of heroes">
|
<li *ngFor="let hero of heroes">
|
||||||
<a routerLink="/detail/{{hero.id}}">
|
<a routerLink="/detail/{{hero.id}}">
|
||||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<button class="delete" title="delete hero"
|
||||||
</ul>
|
(click)="delete(hero)">x</button>
|
||||||
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
|
</li>
|
||||||
|
</ul>
|
|
@ -1,52 +1,89 @@
|
||||||
/* HeroesComponent's private CSS styles */
|
/* HeroesComponent's private CSS styles */
|
||||||
.heroes {
|
.heroes {
|
||||||
margin: 0 0 2em 0;
|
margin: 0 0 2em 0;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 15em;
|
width: 15em;
|
||||||
}
|
}
|
||||||
.heroes li {
|
|
||||||
cursor: pointer;
|
input {
|
||||||
position: relative;
|
display: block;
|
||||||
left: 0;
|
width: 100%;
|
||||||
background-color: #EEE;
|
padding: .5rem;
|
||||||
margin: .5em;
|
margin: 1rem 0;
|
||||||
padding: .3em 0;
|
box-sizing: border-box;
|
||||||
height: 1.6em;
|
}
|
||||||
border-radius: 4px;
|
|
||||||
}
|
.heroes li {
|
||||||
.heroes li:hover {
|
position: relative;
|
||||||
color: #2c3a41;
|
cursor: pointer;
|
||||||
background-color: #e6e6e6;
|
}
|
||||||
left: .1em;
|
|
||||||
}
|
.heroes li:hover {
|
||||||
.heroes li.selected {
|
left: .1em;
|
||||||
background-color: black;
|
}
|
||||||
color: white;
|
|
||||||
}
|
.heroes a {
|
||||||
.heroes li.selected:hover {
|
color: #333;
|
||||||
background-color: #505050;
|
text-decoration: none;
|
||||||
color: white;
|
background-color: #EEE;
|
||||||
}
|
margin: .5em;
|
||||||
.heroes li.selected:active {
|
padding: .3em 0;
|
||||||
background-color: black;
|
height: 1.6em;
|
||||||
color: white;
|
border-radius: 4px;
|
||||||
}
|
display: block;
|
||||||
.heroes .badge {
|
width: 100%;
|
||||||
display: inline-block;
|
}
|
||||||
font-size: small;
|
|
||||||
color: white;
|
.heroes a:hover {
|
||||||
padding: 0.8em 0.7em 0 0.7em;
|
color: #2c3a41;
|
||||||
background-color:#405061;
|
background-color: #e6e6e6;
|
||||||
line-height: 1em;
|
}
|
||||||
position: relative;
|
|
||||||
left: -1px;
|
.heroes a:active {
|
||||||
top: -4px;
|
background-color: #525252;
|
||||||
height: 1.8em;
|
color: #fafafa;
|
||||||
margin-right: .8em;
|
}
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
}
|
.heroes .badge {
|
||||||
|
display: inline-block;
|
||||||
input {
|
font-size: small;
|
||||||
padding: .5rem;
|
color: white;
|
||||||
}
|
padding: 0.8em 0.7em 0 0.7em;
|
||||||
|
background-color:#405061;
|
||||||
|
line-height: 1em;
|
||||||
|
position: relative;
|
||||||
|
left: -1px;
|
||||||
|
top: -4px;
|
||||||
|
height: 1.8em;
|
||||||
|
min-width: 16px;
|
||||||
|
text-align: right;
|
||||||
|
margin-right: .8em;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button {
|
||||||
|
padding: .5rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: #42545C;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.delete {
|
||||||
|
position: absolute;
|
||||||
|
left: 210px;
|
||||||
|
top: 5px;
|
||||||
|
background-color: white;
|
||||||
|
color: #525252;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
padding: 1px 10px 3px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.delete:hover {
|
||||||
|
background-color: #525252;
|
||||||
|
color: white;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { Hero } from '../../interface/hero.interface';
|
import { Hero } from '../../interface/hero.interface';
|
||||||
import { HeroService } from '../../services/hero.service';
|
import { HeroService } from '../../services/hero.service';
|
||||||
import { MessageService } from '../../services/message.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-heroes',
|
selector: 'app-heroes',
|
||||||
|
@ -9,23 +9,31 @@ import { MessageService } from '../../services/message.service';
|
||||||
styleUrls: ['./heroes.component.scss']
|
styleUrls: ['./heroes.component.scss']
|
||||||
})
|
})
|
||||||
export class HeroesComponent implements OnInit {
|
export class HeroesComponent implements OnInit {
|
||||||
|
heroes: Hero[];
|
||||||
|
|
||||||
selectedHero?: Hero;
|
constructor(private heroService: HeroService) { }
|
||||||
|
|
||||||
heroes: Hero[] = [];
|
|
||||||
|
|
||||||
constructor(private heroService: HeroService, private messageService: MessageService) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.getHeroes();
|
this.getHeroes();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelect(hero: Hero): void {
|
|
||||||
console.log("heroes.component.ts excite onSelect")
|
|
||||||
this.selectedHero = hero;
|
|
||||||
this.messageService.add(`HeroesComponent: Selected hero id=${hero.id}`);
|
|
||||||
}
|
|
||||||
getHeroes(): void {
|
getHeroes(): void {
|
||||||
this.heroService.getHeroes()
|
this.heroService.getHeroes()
|
||||||
.subscribe(heroes => this.heroes = heroes); }
|
.subscribe(heroes => this.heroes = heroes);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(name: string): void {
|
||||||
|
name = name.trim();
|
||||||
|
if (!name) { return; }
|
||||||
|
this.heroService.addHero({ name } as Hero)
|
||||||
|
.subscribe(hero => {
|
||||||
|
this.heroes.push(hero);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(hero: Hero): void {
|
||||||
|
this.heroes = this.heroes.filter(h => h !== hero);
|
||||||
|
this.heroService.deleteHero(hero.id).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,27 +1,122 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { catchError, map, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { Hero } from '../interface/hero.interface';
|
import { Hero } from '../interface/hero.interface';
|
||||||
import { HEROES } from '../interface/mock-heroes';
|
|
||||||
import { MessageService } from './message.service';
|
import { MessageService } from './message.service';
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
@Injectable({ providedIn: 'root' })
|
||||||
})
|
|
||||||
export class HeroService {
|
export class HeroService {
|
||||||
|
|
||||||
constructor(private messageService: MessageService) { }
|
private heroesUrl = 'api/heroes'; // URL to web api
|
||||||
|
|
||||||
|
httpOptions = {
|
||||||
|
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
|
private messageService: MessageService) { }
|
||||||
|
|
||||||
|
/** GET heroes from the server */
|
||||||
getHeroes(): Observable<Hero[]> {
|
getHeroes(): Observable<Hero[]> {
|
||||||
const heroes = of(HEROES);
|
return this.http.get<Hero[]>(this.heroesUrl)
|
||||||
this.messageService.add('HeroService: fetched heroes');
|
.pipe(
|
||||||
return heroes;
|
tap(_ => this.log('fetched heroes')),
|
||||||
|
catchError(this.handleError<Hero[]>('getHeroes', []))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** GET hero by id. Return `undefined` when id not found */
|
||||||
|
getHeroNo404<Data>(id: number): Observable<Hero> {
|
||||||
|
const url = `${this.heroesUrl}/?id=${id}`;
|
||||||
|
return this.http.get<Hero[]>(url)
|
||||||
|
.pipe(
|
||||||
|
map(heroes => heroes[0]), // returns a {0|1} element array
|
||||||
|
tap(h => {
|
||||||
|
const outcome = h ? `fetched` : `did not find`;
|
||||||
|
this.log(`${outcome} hero id=${id}`);
|
||||||
|
}),
|
||||||
|
catchError(this.handleError<Hero>(`getHero id=${id}`))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET hero by id. Will 404 if id not found */
|
||||||
getHero(id: number): Observable<Hero> {
|
getHero(id: number): Observable<Hero> {
|
||||||
// For now, assume that a hero with the specified `id` always exists.
|
const url = `${this.heroesUrl}/${id}`;
|
||||||
// Error handling will be added in the next step of the tutorial.
|
return this.http.get<Hero>(url).pipe(
|
||||||
const hero = HEROES.find(h => h.id === id) as Hero;
|
tap(_ => this.log(`fetched hero id=${id}`)),
|
||||||
this.messageService.add(`HeroService: fetched hero id=${id}`);
|
catchError(this.handleError<Hero>(`getHero id=${id}`))
|
||||||
return of(hero);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GET heroes whose name contains search term */
|
||||||
|
searchHeroes(term: string): Observable<Hero[]> {
|
||||||
|
if (!term.trim()) {
|
||||||
|
// if not search term, return empty hero array.
|
||||||
|
return of([]);
|
||||||
|
}
|
||||||
|
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
|
||||||
|
tap(x => x.length ?
|
||||||
|
this.log(`found heroes matching "${term}"`) :
|
||||||
|
this.log(`no heroes matching "${term}"`)),
|
||||||
|
catchError(this.handleError<Hero[]>('searchHeroes', []))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////// Save methods //////////
|
||||||
|
|
||||||
|
/** POST: add a new hero to the server */
|
||||||
|
addHero(hero: Hero): Observable<Hero> {
|
||||||
|
return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
|
||||||
|
tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
|
||||||
|
catchError(this.handleError<Hero>('addHero'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DELETE: delete the hero from the server */
|
||||||
|
deleteHero(id: number): Observable<Hero> {
|
||||||
|
const url = `${this.heroesUrl}/${id}`;
|
||||||
|
|
||||||
|
return this.http.delete<Hero>(url, this.httpOptions).pipe(
|
||||||
|
tap(_ => this.log(`deleted hero id=${id}`)),
|
||||||
|
catchError(this.handleError<Hero>('deleteHero'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** PUT: update the hero on the server */
|
||||||
|
updateHero(hero: Hero): Observable<any> {
|
||||||
|
return this.http.put(this.heroesUrl, hero, this.httpOptions).pipe(
|
||||||
|
tap(_ => this.log(`updated hero id=${hero.id}`)),
|
||||||
|
catchError(this.handleError<any>('updateHero'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Http operation that failed.
|
||||||
|
* Let the app continue.
|
||||||
|
* @param operation - name of the operation that failed
|
||||||
|
* @param result - optional value to return as the observable result
|
||||||
|
*/
|
||||||
|
private handleError<T>(operation = 'operation', result?: T) {
|
||||||
|
return (error: any): Observable<T> => {
|
||||||
|
|
||||||
|
// TODO: send the error to remote logging infrastructure
|
||||||
|
console.error(error); // log to console instead
|
||||||
|
|
||||||
|
// TODO: better job of transforming error for user consumption
|
||||||
|
this.log(`${operation} failed: ${error.message}`);
|
||||||
|
|
||||||
|
// Let the app keep running by returning an empty result.
|
||||||
|
return of(result as T);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log a HeroService message with the MessageService */
|
||||||
|
private log(message: string) {
|
||||||
|
this.messageService.add(`HeroService: ${message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { InMemoryDataService } from './in-memory-data.service';
|
||||||
|
|
||||||
|
describe('InMemoryDataService', () => {
|
||||||
|
let service: InMemoryDataService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(InMemoryDataService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { InMemoryDbService } from 'angular-in-memory-web-api';
|
||||||
|
import { Hero } from '../interface/hero.interface';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class InMemoryDataService implements InMemoryDbService {
|
||||||
|
createDb() {
|
||||||
|
const heroes = [
|
||||||
|
{ id: 11, name: 'Dr Nice' },
|
||||||
|
{ id: 12, name: 'Narco' },
|
||||||
|
{ id: 13, name: 'Bombasto' },
|
||||||
|
{ id: 14, name: 'Celeritas' },
|
||||||
|
{ id: 15, name: 'Magneta' },
|
||||||
|
{ id: 16, name: 'RubberMan' },
|
||||||
|
{ id: 17, name: 'Dynama' },
|
||||||
|
{ id: 18, name: 'Dr IQ' },
|
||||||
|
{ id: 19, name: 'Magma' },
|
||||||
|
{ id: 20, name: 'Tornado' }
|
||||||
|
];
|
||||||
|
return {heroes};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides the genId method to ensure that a hero always has an id.
|
||||||
|
// If the heroes array is empty,
|
||||||
|
// the method below returns the initial number (11).
|
||||||
|
// if the heroes array is not empty, the method below returns the highest
|
||||||
|
// hero id + 1.
|
||||||
|
genId(heroes: Hero[]): number {
|
||||||
|
return heroes.length > 0 ? Math.max(...heroes.map(hero => hero.id)) + 1 : 11;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue