Angular Tour of Heroes: suite et fin !
This commit is contained in:
		@@ -12,6 +12,7 @@ import { DashboardComponent } from './dashboard/dashboard.component';
 | 
			
		||||
 | 
			
		||||
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
 | 
			
		||||
import { InMemoryDataService }  from './in-memory-data.service';
 | 
			
		||||
import { HeroSearchComponent } from './hero-search/hero-search.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@@ -19,7 +20,8 @@ import { InMemoryDataService }  from './in-memory-data.service';
 | 
			
		||||
    HeroesComponent,
 | 
			
		||||
    HeroDetailComponent,
 | 
			
		||||
    MessagesComponent,
 | 
			
		||||
    DashboardComponent
 | 
			
		||||
    DashboardComponent,
 | 
			
		||||
    HeroSearchComponent
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
<h3>Top Heroes</h3>
 | 
			
		||||
<div class="grid grid-pad">
 | 
			
		||||
  <a *ngFor="let hero of heroes" class="col-1-4"
 | 
			
		||||
                                 routerLink="/detail/{{hero.id}}">
 | 
			
		||||
      routerLink="/detail/{{hero.id}}">
 | 
			
		||||
    <div class="module hero">
 | 
			
		||||
      <h4>{{hero.name}}</h4>
 | 
			
		||||
    </div>
 | 
			
		||||
  </a>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<app-hero-search></app-hero-search>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
/* HeroSearch private styles */
 | 
			
		||||
.search-result li {
 | 
			
		||||
  border-bottom: 1px solid gray;
 | 
			
		||||
  border-left: 1px solid gray;
 | 
			
		||||
  border-right: 1px solid gray;
 | 
			
		||||
  width: 195px;
 | 
			
		||||
  height: 16px;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-result li:hover {
 | 
			
		||||
  background-color: #607D8B;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-result li a {
 | 
			
		||||
  color: #888;
 | 
			
		||||
  display: block;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-result li a:hover {
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
.search-result li a:active {
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
#search-box {
 | 
			
		||||
  width: 200px;
 | 
			
		||||
  height: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ul.search-result {
 | 
			
		||||
  margin-top: 0;
 | 
			
		||||
  padding-left: 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
<div id="search-component">
 | 
			
		||||
  <h4><label for="search-box">Hero Search</label></h4>
 | 
			
		||||
 | 
			
		||||
  <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,25 @@
 | 
			
		||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { HeroSearchComponent } from './hero-search.component';
 | 
			
		||||
 | 
			
		||||
describe('HeroSearchComponent', () => {
 | 
			
		||||
  let component: HeroSearchComponent;
 | 
			
		||||
  let fixture: ComponentFixture<HeroSearchComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async(() => {
 | 
			
		||||
    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 '../hero';
 | 
			
		||||
import { HeroService } from '../hero.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-hero-search',
 | 
			
		||||
  templateUrl: './hero-search.component.html',
 | 
			
		||||
  styleUrls: ['./hero-search.component.css']
 | 
			
		||||
})
 | 
			
		||||
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 terme changes
 | 
			
		||||
      switchMap((term: string) => this.heroService.searchHeroes(term)),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -67,6 +67,17 @@ export class HeroService {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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(_ => this.log(`found heroes matching "${term}"`)),
 | 
			
		||||
      catchError(this.handleError<Hero[]>('searchHeroes', []))
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Handle Http operation that failed.
 | 
			
		||||
   * Let the app continue.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user