import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { MessagesComponent } from './messages/messages.component';
import { DashboardComponent } from './dashboard/dashboard.component';
+import { HeroSearchComponent } from './hero-search/hero-search.component';
@NgModule({
declarations: [
HeroesComponent,
HeroDetailComponent,
MessagesComponent,
- DashboardComponent
+ DashboardComponent,
+ HeroSearchComponent
],
imports: [
BrowserModule,
</div>
</a>
</div>
+
+<app-hero-search></app-hero-search>
+
--- /dev/null
+/* 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;
+}
--- /dev/null
+<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>
--- /dev/null
+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();
+ });
+});
--- /dev/null
+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 term changes
+ switchMap((term: string) => this.heroService.searchHeroes(term)),
+ );
+ }
+}
);
}
+ /* 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', []))
+ );
+ }
+
/**
* Handle Http operation that failed.
* Let the app continue.