Angular 5公式Tutorialの作例を改善する


Angularの公式Tutorialは、全体をとおしてひとつの作例「Angular Example - Tour of Heroes: Part 6 」をつくります。この解説を下じきに、もっと細かくコードの動きが確かめられるように、説明の仕方やサンプルも改めて「Angular 5入門」を書きました。そのとき、公式の作例にいくつか気づいて直した点があります。それらをご紹介しましょう。

要らないコードを除く

「Routing」の「Remove dead code (optional)」は、書き直しの結果要らなくなったコードを除いています。これに加えてもうひとつ削った方がよいのは、HeroDetailComponentクラスのデコレータ関数Input()です。

import { Component, OnInit, Input } from '@angular/core';

export class HeroDetailComponent implements OnInit {
    @Input() hero: Hero;

}

コンポーネントの詳細情報(hero-detail.component)をリスト表示(heroes.component)から分け、選択されたデータをサービスから得るようにしたからです。親だったリスト表示のコンポーネントから参照を得るためにプロパティ(hero)に添えていたデコレータ関数Input()は意味がなくなります。

import { Component, OnInit} from '@angular/core';

export class HeroDetailComponent implements OnInit {
    hero: Hero;

}

公式Tutorialにもこの修正が加えられそうです(「refactor(aio): refining code of tutorial 7 routing」)。

リストを空にするとデータが加えられない

まず、リスト表示の画面ですべての項目を削除します(図001)。

図001■リスト項目がなくなった

つぎに、新たな項目をテキストフィールドに入力して、[add]ボタンを押します(図002)。

図002■新たな項目を入力する

すると、番号もテキストも空の項目が加えられます(図003)。ブラウザのコンソールを確かめると、つぎのようなエラーが示されました。

body: {error: "Collection 'heroes' id type is non-numeric or unknown. Can only generate numeric ids."}

図003■番号とテキストが空の項目

データの配列が空になって、プロパティidをもつオブジェクトがなくなると、新たな番号はつくれず、データも加えられないようです。

HttpClient.put()メソッドを使えば、データにid番号を定めて追加できます。リストが空のときは、つぎのようにidは初期値にしてHttpClient.put()メソッドでデータを送ればよいでしょう。

export class HeroService {

    // addHero(hero: Hero): Observable<Hero> {
    addHero(hero: Hero, numHeros: number): Observable<Hero> {
        let heroOvserbable;
        // return this.http.post<Hero>(this.herosUrl, hero, httpOptions)
        if (numHeros === 0) {
            hero.id = 11;
            heroOvserbable = this.http.put(this.herosUrl, hero, httpOptions)
        } else {
            heroOvserbable = this.http.post<Hero>(this.herosUrl, hero, httpOptions);
        }
        return heroOvserbable

    }

}

ただし、サービスのクラス(HeroService)はデータの配列を知りません。ですから、リスト表示クラス(HerosComponent)がデータ追加(add()メソッド)のためにサービス(HeroService)のメソッド(addHero())を呼び出すとき、第2引数としてデータ(heros)の長さ(Array.lengthプロパティ)を渡します。

export class HerosComponent implements OnInit {

    add(name: string): void {

        // this.heroService.addHero({name} as Hero)
        this.heroService.addHero({name} as Hero, this.heros.length)

    }

}

これで、リストを空にしても新たな項目が加えられます。これらの手直しをした作例は「Angular Example - Tour of Heroes: Part 6 revised」に掲げました。

Angular in-memory-web-apiモジュールのメソッドをオーバーライドする

リストを空にすると新たな項目がつくれないのは、Angular in-memory-web-api moduleの仕様にもとづくようです。デフォルトのid番号を返すBackendService.genIdDefault()メソッドは、BackendService.isCollectionIdNumeric()メソッドで、リストのデータがあり、さらに数値のidプロパティをもつかどうか確かめます。そうでなければ、デフォルトのid番号はできず、エラーになるのです。

データにid番号を振るのはBackendService.genId()メソッドです。このメソッドは、InMemoryDataServiceクラス(in-memory-data.service)でオーバーライドできます(「Tutorial example has a problem to enter new item into an empty list」参照)。すると、前項のようにサービス(HeroService)やリスト表示(HerosComponent)のクラスは書き替えずに済むのです。

InMemoryDataServiceクラスに定めてオーバーライドするつぎのgenId()メソッドは、データがあればid番号の最大値 + 1、なければ初期値を返します。この作例は"Angular Example - Tour of Heroes: Part 6"に掲げられています。

export class InMemoryDataService implements InMemoryDbService {

    genId(heroines: Heroine[]): number {
        return heroines.length > 0 ? Math.max(...heroines.map(heroine => heroine.id)) + 1 : 11;
    }
}

おまけ

細かいところで、もうひとつつけ加えておきます。サービスのクラス(HeroService)の検索メソッド(searchHeroes())で、URLを直書きしているところです。もとのパスはプロパティ(heroesUrl)に定めてあります。後あとの修正も考えるなら、プロパティを用いるべきでしょう。

export class HeroService {

    searchHeroes(term: string): Observable<Hero[]> {

        const url = `${this.heroesUrl}/?name=${term}`;
        // return this.http.get<Hero[]>(`api/heroes/?name=${term}`)
        return this.http.get<Hero[]>(url)

    }

}