| @@ -2226,6 +2226,11 @@ | |||||
| "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", | ||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "angular-in-memory-web-api": { | |||||
| "version": "0.9.0", | |||||
| "resolved": "https://registry.npmjs.org/angular-in-memory-web-api/-/angular-in-memory-web-api-0.9.0.tgz", | |||||
| "integrity": "sha512-//PiJ5qb1+Yf/N7270ioQqR2laf4/Irjavg+M+WEn8y4At9LUoYgbQ5HVwvM5xUTlVlL0XkbJRLxREcGGNdIEw==" | |||||
| }, | |||||
| "ansi-colors": { | "ansi-colors": { | ||||
| "version": "3.2.4", | "version": "3.2.4", | ||||
| "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", | ||||
| @@ -19,6 +19,7 @@ | |||||
| "@angular/platform-browser": "~8.2.9", | "@angular/platform-browser": "~8.2.9", | ||||
| "@angular/platform-browser-dynamic": "~8.2.9", | "@angular/platform-browser-dynamic": "~8.2.9", | ||||
| "@angular/router": "~8.2.9", | "@angular/router": "~8.2.9", | ||||
| "angular-in-memory-web-api": "^0.9.0", | |||||
| "rxjs": "~6.4.0", | "rxjs": "~6.4.0", | ||||
| "tslib": "^1.10.0", | "tslib": "^1.10.0", | ||||
| "zone.js": "~0.9.1" | "zone.js": "~0.9.1" | ||||
| @@ -1,13 +1,16 @@ | |||||
| import { BrowserModule } from '@angular/platform-browser'; | |||||
| import { NgModule } from '@angular/core'; | |||||
| import {BrowserModule} from '@angular/platform-browser'; | |||||
| import {NgModule} from '@angular/core'; | |||||
| import { AppComponent } from './app.component'; | |||||
| import { HeroesComponent } from './heroes/heroes.component'; | |||||
| import {AppComponent} from './app.component'; | |||||
| import {HeroesComponent} from './heroes/heroes.component'; | |||||
| import {FormsModule} from '@angular/forms'; | import {FormsModule} from '@angular/forms'; | ||||
| import { HeroDetailComponent } from './hero-detail/hero-detail.component'; | |||||
| import { MessagesComponent } from './messages/messages.component'; | |||||
| import { AppRoutingModule } from './app-routing.module'; | |||||
| import { DashboardComponent } from './dashboard/dashboard.component'; | |||||
| import {HeroDetailComponent} from './hero-detail/hero-detail.component'; | |||||
| import {MessagesComponent} from './messages/messages.component'; | |||||
| import {AppRoutingModule} from './app-routing.module'; | |||||
| import {DashboardComponent} from './dashboard/dashboard.component'; | |||||
| import {HttpClientModule} from "@angular/common/http"; | |||||
| import {HttpClientInMemoryWebApiModule} from "angular-in-memory-web-api"; | |||||
| import {InMemoryDataService} from "./in-memory-data.service"; | |||||
| @NgModule({ | @NgModule({ | ||||
| declarations: [ | declarations: [ | ||||
| @@ -20,9 +23,14 @@ import { DashboardComponent } from './dashboard/dashboard.component'; | |||||
| imports: [ | imports: [ | ||||
| BrowserModule, | BrowserModule, | ||||
| FormsModule, | FormsModule, | ||||
| AppRoutingModule | |||||
| AppRoutingModule, | |||||
| HttpClientModule, | |||||
| HttpClientInMemoryWebApiModule.forRoot( | |||||
| InMemoryDataService, {dataEncapsulation: false} | |||||
| ) | |||||
| ], | ], | ||||
| providers: [], | providers: [], | ||||
| bootstrap: [AppComponent] | bootstrap: [AppComponent] | ||||
| }) | }) | ||||
| export class AppModule { } | |||||
| export class AppModule { | |||||
| } | |||||
| @@ -8,3 +8,4 @@ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <button (click)="goBack()">go back</button> | <button (click)="goBack()">go back</button> | ||||
| <button (click)="save()">save</button> | |||||
| @@ -36,4 +36,9 @@ export class HeroDetailComponent implements OnInit { | |||||
| goBack() { | goBack() { | ||||
| this.location.back(); | this.location.back(); | ||||
| } | } | ||||
| save() { | |||||
| this.heroService.udpateHero(this.hero) | |||||
| .subscribe(() => this.goBack()); | |||||
| } | |||||
| } | } | ||||
| @@ -1,24 +1,79 @@ | |||||
| import { Injectable } from '@angular/core'; | |||||
| import {Injectable} from '@angular/core'; | |||||
| import { Hero } from './hero'; | |||||
| import { HEROES } from './mock-heroes'; | |||||
| import { Observable, of } from 'rxjs'; | |||||
| import { MessageService } from './message.service'; | |||||
| import {Hero} from './hero'; | |||||
| import {Observable, of} from 'rxjs'; | |||||
| import {MessageService} from './message.service'; | |||||
| import {HttpClient, HttpHeaders} from "@angular/common/http"; | |||||
| import {catchError, tap} from "rxjs/operators"; | |||||
| @Injectable({ | @Injectable({ | ||||
| providedIn: 'root' | providedIn: 'root' | ||||
| }) | }) | ||||
| export class HeroService { | export class HeroService { | ||||
| constructor(private messageService: MessageService) { } | |||||
| private heroesUrl = 'api/heroes'; | |||||
| httpOptions = { | |||||
| headers: new HttpHeaders({ 'Content-Type': 'application/json' }) | |||||
| }; | |||||
| constructor( | |||||
| private http: HttpClient, | |||||
| private messageService: MessageService) { | |||||
| } | |||||
| getHeroes(): Observable<Hero[]> { | getHeroes(): Observable<Hero[]> { | ||||
| this.messageService.add('HeroService: fetched heroes') | this.messageService.add('HeroService: fetched heroes') | ||||
| return of(HEROES); | |||||
| return this.http.get<Hero[]>(this.heroesUrl) | |||||
| .pipe( | |||||
| tap(_ => this.log('feteched heroes')), | |||||
| catchError(this.handleError<Hero[]>('getHeroes', [])) | |||||
| ); | |||||
| } | } | ||||
| getHero(id: number): Observable<Hero> { | getHero(id: number): Observable<Hero> { | ||||
| this.messageService.add(`HeroService: fetched hero id=${id}`); | |||||
| return of(HEROES.find(hero => hero.id === id)); | |||||
| const url = `${this.heroesUrl}/${id}`; | |||||
| return this.http.get<Hero>(url).pipe( | |||||
| tap(_ => this.log(`fetched hero id=${id}`)), | |||||
| catchError(this.handleError<Hero>(`getHero id=${id}`)) | |||||
| ); | |||||
| } | |||||
| 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')) | |||||
| ); | |||||
| } | |||||
| deleteHero (hero: Hero | number): Observable<Hero> { | |||||
| const id = typeof hero === 'number' ? hero : hero.id; | |||||
| 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')) | |||||
| ); | |||||
| } | |||||
| private log(message: string) { | |||||
| this.messageService.add(`HeroService: ${message}`); | |||||
| } | |||||
| handleError<T>(operation: string = 'operation', result?: T) { | |||||
| return (error: any): Observable<T> => { | |||||
| console.error(error); | |||||
| this.log(`${operation} failed: ${error.message}`); | |||||
| return of(result as T); | |||||
| } | |||||
| } | |||||
| udpateHero(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')) | |||||
| ); | |||||
| } | } | ||||
| } | } | ||||
| @@ -49,3 +49,25 @@ | |||||
| margin-right: .8em; | margin-right: .8em; | ||||
| border-radius: 4px 0 0 4px; | border-radius: 4px 0 0 4px; | ||||
| } | } | ||||
| button { | |||||
| background-color: #eee; | |||||
| border: none; | |||||
| padding: 5px 10px; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| cursor: hand; | |||||
| font-family: Arial; | |||||
| } | |||||
| button:hover { | |||||
| background-color: #cfd8dc; | |||||
| } | |||||
| button.delete { | |||||
| position: relative; | |||||
| left: 194px; | |||||
| top: -32px; | |||||
| background-color: gray !important; | |||||
| color: white; | |||||
| } | |||||
| @@ -1,8 +1,19 @@ | |||||
| <h2>My Heroes</h2> | <h2>My Heroes</h2> | ||||
| <div> | |||||
| <label>Hero name: | |||||
| <input #heroName /> | |||||
| </label> | |||||
| <!-- (click) passes input value to add() and then clears the input --> | |||||
| <button (click)="add(heroName.value); heroName.value=''"> | |||||
| add | |||||
| </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> | ||||
| <button class="delete" title="delete hero" | |||||
| (click)="delete(hero)">x</button> | |||||
| </li> | </li> | ||||
| </ul> | </ul> | ||||
| @@ -22,4 +22,18 @@ export class HeroesComponent implements OnInit { | |||||
| 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).subscribe(); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,32 @@ | |||||
| import { Injectable } from '@angular/core'; | |||||
| import {InMemoryDbService} from "angular-in-memory-web-api"; | |||||
| import {Observable} from "rxjs"; | |||||
| import {Hero} from "./hero"; | |||||
| @Injectable({ | |||||
| providedIn: 'root' | |||||
| }) | |||||
| export class InMemoryDataService implements InMemoryDbService { | |||||
| constructor() { } | |||||
| 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}; | |||||
| } | |||||
| genId(heroes: Hero[]): number { | |||||
| return heroes.length > 0 ? Math.max(...heroes.map(hero => hero.id)) + 1 : 11; | |||||
| } | |||||
| } | |||||