Hero项目分析(基于Angular架构)

HeroServe

使用httpClient中的get(),put(),delete(),update()方法写出之后其他xxxx.ts文件将要调用的增删改查方法。

hero.service.ts

声明httpOptions,heroesUrl(定义根目录),http(创建的HttpClient实例),messageService(记录各类信息)

以下是声明的增删改查方法

getHeroes (): Observable<Hero[]> {
    return this.http.get<Hero[]>(this.heroesUrl)
      .pipe(
        tap(_ => this.log('fetched heroes')),
        catchError(this.handleError('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> {
    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}`))
    );
  }
 
  /* 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(_ => this.log(`found 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, httpOptions).pipe(
      tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)),
      catchError(this.handleError<Hero>('addHero'))
    );
  }
 
  /** DELETE: delete the hero from the server */
  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, 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, 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}`);
  }

in-memory-data.service.ts

这个文件作为模拟web api来暂存数据

app.module.ts

在这个文件中是申明注册各个模块

HeroesComponent

heroes.component.html

显示所有的英雄,并有增删查的功能

<button (click)="add(heroName.value); heroName.value=''">
    add
</button>

<a routerLink="/detail/{{hero.id}}">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
</a>

<button class="delete" title="delete hero"
      (click)="delete(hero)">
  x
</button>

heroes.component.ts

通过调用hero.service.ts中的方法,并将返回结果订阅至Observable对象中,以实现增删查功能,并且结果可以实时在页面上显示。

getHeroes(): void {
    this.heroService.getHeroes()
    .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();
  }

HeroDetailComponent

hero-detail.component.html

具体显示每个英雄信息

<div *ngIf="hero">
  <h2>{{hero.name | uppercase}} Details</h2>
  <div><span>id: </span>{{hero.id}}</div>
  <div>
    <label>name:
      <input [(ngModel)]="hero.name" placeholder="name"/>
    </label>
  </div>
  <button (click)="goBack()">go back</button><!--返回上个页面-->
  <button (click)="save()">save</button><!--保存修改了的数据,以在页面上实时显示-->
</div>

[(ngModel)] 是 Angular 的双向数据绑定语法

这里把 hero.name 属性绑定到了 HTML 的 textbox 元素上,以便数据流可以双向流动:从 hero.name 属性流动到 textbox,并且从 textbox 流回到 hero.name

使用 *ngIf 隐藏空白的详情

但模板中的绑定表达式引用了 selectedHero 的属性(表达式为 {{selectedHero.name}}),这必然会失败,因为你还没选过英雄呢。

修复:该组件应该只有当 selectedHero 存在时才显示所选英雄的详情。当 selectedHeroundefined 时,ngIf 从 DOM 中移除了英雄详情。因此也就不用担心 selectedHero 的绑定了。

当用户选择一个英雄时,selectedHero 也就有了值,并且 ngIf 把英雄的详情放回到 DOM 中。

hero-detail.component.ts

调用hero.service.ts中的方法来实现页面功能。

 getHero(): void {
    const id = +this.route.snapshot.paramMap.get('id');
    this.heroService.getHero(id)
      .subscribe(hero => this.hero = hero);
  }
 
  goBack(): void {
    this.location.back();
  }
 
 save(): void {
    this.heroService.updateHero(this.hero)
      .subscribe(() => this.goBack());
  }

DashboardComponent

dashboard.component.html

这是一个导航栏,可以定位到任意一个英雄的详情

<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <a *ngFor="let hero of heroes" class="col-1-4"
      routerLink="/detail/{{hero.id}}">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </a>
</div>

<app-hero-search></app-hero-search>

HeroSearchComponent

Hero-search.component.html

*ngFor 是在一个名叫 heroes$ 的列表上迭代,而不是 heroes

<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>

$ 是一个命名惯例,用来表明 heroes$ 是一个 Observable,而不是数组。

*ngFor 不能直接使用 Observable。 不过,它后面还有一个管道字符(|),后面紧跟着一个 async,它表示 Angular 的 AsyncPipe

AsyncPipe 会自动订阅到 Observable,不用再在组件类中订阅了。

每当用户在文本框中输入时,这个事件绑定就会使用文本框的值(搜索词)调用 search() 函数。 searchTerms 变成了一个能发出搜索词的稳定的流。

hero-search.component.ts

实现搜索功能

 search(term: string): void {
    this.searchTerms.next(term);
  }
 

比并且节省服务器资源和网络流量,并最终返回一个可及时给出英雄搜索结果的可观察对象(每次都是 Hero[] )。

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)),
    );
  }
  • 在传出最终字符串之前debounceTime(300) 将会等待,直到新增字符串的事件暂停了 300 毫秒。 你实际发起请求的间隔永远不会小于 300ms。
  • distinctUntilChanged() 会确保只在过滤条件变化时才发送请求。
  • switchMap() 会为每个从debounce 和distinctUntilChanged中通过的搜索词调用搜索服务。 它会取消并丢弃以前的搜索可观察对象,只保留最近的。
发布了51 篇原创文章 · 获赞 0 · 访问量 714

猜你喜欢

转载自blog.csdn.net/lxy1740/article/details/104308930