EclipseでAngularJSのチュートリアル HTTP

今回は、「HTTP」ですね。今回も間違ってる可能性大なので、お時間のある方のみご覧ください。一応、動いてはいますよ~。

The HttpModule is not a core NgModule. HttpModule is Angular's optional approach to web access. It exists as a separate add-on module called @angular/http and is shipped in a separate script file as part of the Angular npm package.

You're ready to import from @angular/http because systemjs.config configured SystemJS to load that library when you need it.

「node_modules」>「@angular」>「http」がインストールされてれば利用できる状態になっているということのようです。

f:id:ts0818:20170917150043j:plain

FN1707005 | Angular 4入門 08: HTTPサービスでデータを取得・保存する | HTML5 : テクニカルノート

 

@ angular / httpライブラリのHttpModuleを使えるようにする

「app.module.ts」 ファイルに、「@angular/http」の「HttpModule」をインポートします。

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';  // 追加

import { AppComponent }        from './app.component';
import { DashboardComponent }  from './dashboard.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent }     from './heroes.component';
import { HeroService }         from './hero.service';

import { AppRoutingModule }    from './app-routing.module';

@NgModule({
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    HttpModule // 追加
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

 

Simulate the web APIを使う

本当は、どこかのサーバー上のWeb APIを利用するのが良いようですが、メモリで動くシミュレーションのモジュールangular-in-memory-web-apiを使っていくようです。

「app.module.ts」 ファイルに「angular-in-memory-web-api」とデータ用の「./in-memory-data.service」(後で作成の「in-memory-data.service.ts」)をインポートします。

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';  // 追加

// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';  // 追加
import { InMemoryDataService }  from './in-memory-data.service';  // 追加

import { AppComponent }        from './app.component';
import { DashboardComponent }  from './dashboard.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent }     from './heroes.component';
import { HeroService }         from './hero.service';

import { AppRoutingModule }    from './app-routing.module';

@NgModule({
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    HttpModule, // 追加
    InMemoryWebApiModule.forRoot(InMemoryDataService),  // 追加
    AppRoutingModule
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

で、エラー。「Cannot find module 'angular-in-memory-web-api'」

よくエラーの出るチュートリアルですな。(自分のやり方が悪いんだとは思いますが、初心者に優しくないですな)

そも、チュートリアルとは?

これは、 「個別指導、家庭教師」 という意味です。 転じて、 「基本操作などを教えるプログラム」 という意味で使われます。 一番最初に操作方法などを説明するプログラム・教材等のことで、 「あなた一人に対して教育する(個別指導)」という意味で、 このチュートリアルという言葉が使われています。 主に基本操作や基礎的なものを教えるためのプログラムのことを言い、 動画や、実際の操作を伴っての説明が多いです。

チュートリアルの意味とは

「Angular DOC」は、スパルタ教師ですね....。

 

すみません、脱線いたしました。

installing angular in memory web api is not recognising @angular/core modules · Issue #5677 · angular/angular-cli · GitHub

⇧  「package.json」に「angular2-in-memory-web-api": "0.0.21"」とかが普通は入ってるらしい。(いまは、「angular-in-memory-web-api」)

私の「C:¥workspace¥firstSpring¥src¥main¥resources¥static¥angular-tour-of-heroes¥package.json」ファイル、そもそもそんなもの存在しない。

f:id:ts0818:20170917173158j:plain

{
  "name": "angular-tour-of-heroes",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^4.2.4",
    "@angular/common": "^4.2.4",
    "@angular/compiler": "^4.2.4",
    "@angular/core": "^4.2.4",
    "@angular/forms": "^4.2.4",
    "@angular/http": "^4.2.4",
    "@angular/platform-browser": "^4.2.4",
    "@angular/platform-browser-dynamic": "^4.2.4",
    "@angular/router": "^4.2.4",
    "core-js": "^2.4.1",
    "rxjs": "^5.4.2",
    "zone.js": "^0.8.14"
  },
  "devDependencies": {
    "@angular/cli": "1.4.1",
    "@angular/compiler-cli": "^4.2.4",
    "@angular/language-service": "^4.2.4",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "~3.1.1",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.2.0",
    "tslint": "~5.3.2",
    "typescript": "~2.3.3"
  }
}

チュートリアルにもインストールしろとかないんだけどな~....自分のような初心者には厳しきチュートリアルですな(涙)。

まぁ、とりあえず、「package.json」に「angular-in-memory-web-api」を追記していきますが、バージョンがどれていいか分からず(涙)。

npm でパッケージのバージョン一覧を確認したりバージョンを指定してインストールしたりする方法 | phiary

⇧  npmの使い方は上記サイト様が詳しいです

npm info angular-in-memory-web-api versions

f:id:ts0818:20170917171901j:plain

とりあえず、最新を入れてしまいますか...。

{
  "name": "angular-tour-of-heroes",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^4.2.4",
    "@angular/common": "^4.2.4",
    "@angular/compiler": "^4.2.4",
    "@angular/core": "^4.2.4",
    "@angular/forms": "^4.2.4",
    "@angular/http": "^4.2.4",
    "@angular/platform-browser": "^4.2.4",
    "@angular/platform-browser-dynamic": "^4.2.4",
    "@angular/router": "^4.2.4",
    "angular-in-memory-web-api": "^0.4.6",
    "core-js": "^2.4.1",
    "rxjs": "^5.4.2",
    "zone.js": "^0.8.14"
  },
  "devDependencies": {
    "@angular/cli": "1.4.1",
    "@angular/compiler-cli": "^4.2.4",
    "@angular/language-service": "^4.2.4",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "~3.1.1",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.2.0",
    "tslint": "~5.3.2",
    "typescript": "~2.3.3"
  }
}
    

そしたらば、コマンドプロンプトで「package.json」ファイルを持つプロジェクトに移動し(今回だと、「C:¥workspace¥firstSpring¥src¥main¥resources¥static¥angular-tour-of-heroes」)、

npm で package.json がカレントディレクトリにない場合 - Please Sleep

⇧  上記サイト様によるとフォルダ移動しなくてもいけるみたいです

npm install angular-in-memory-web-api@0.4.6

f:id:ts0818:20170917175210j:plain

「C:¥workspace¥firstSpring¥src¥main¥resources¥static¥angular-tour-of-heroes¥node_modules」に「angular-in-memory-web-api」がインストールされました。

f:id:ts0818:20170917175900j:plain

エラーも1つになり、ここのエラーは次の工程で「in-memory-data.service.ts」を作ればたぶん消えてくれるでしょう。

f:id:ts0818:20170917180827j:plain

そんでは、「in-memory-data.service.ts」を作成します。「angular-tour-od-heroes」>「app」を選択した状態で右クリックし、「新規(W)」>「その他(O)...」をクリック。

f:id:ts0818:20170917181506j:plain

「TypeScript」>「TypeScript Source File」を選択し、「次へ(N)>」をクリック。

f:id:ts0818:20170917181644j:plain

「フォルダ名(F):」を「in-memory-data.service.ts」とし「完了(F)」。

f:id:ts0818:20170917181749j:plain

「in-memory-data.service.ts」ファイルを編集。

import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryDataService implements InMemoryDbService {
  createDb() {
    const heroes = [
      { id: 0,  name: 'Zero' },
      { id: 11, name: 'Mr. 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};
  }
}

保存すると、「app.module.ts」のエラーが消えました。

f:id:ts0818:20170917182220j:plain

次に、「hero.service.ts」 を編集して、「mock-heroes.ts」ファイルから取得していたデータを、「in-memory-data.service.ts」ファイルから取得するように変更します。

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';

@Injectable()
export class HeroService {

  private heroesUrl = 'api/heroes';  // URL to web api

  constructor(private http: Http) {}

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
               .toPromise()
               .then(response => response.json().data as Hero[])
               .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);  // for demo purposes only
    return Promise.reject(error.message || error);
  }

  getHero(id: number): Promise<hero> {
    return this.getHeroes()
               .then(heroes => heroes.find(hero => hero.id === id));
  }
}

ブラウザを更新してみると、「in-memory-data.service.ts」ファイルのデータが取得されています。

f:id:ts0818:20170917193032j:plain

Updating hero details

同じく、「hero.service.ts」 のidで取得していたgetHero() メソッドも変更していきます。update()メソッドも追加します。

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';

@Injectable()
export class HeroService {

  private heroesUrl = 'api/heroes';  // URL to web api
  private headers = new Headers({'Content-Type': 'application/json'});

  constructor(private http: Http) {}

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
               .toPromise()
               .then(response => response.json().data as Hero[])
               .catch(this.handleError);
  }

  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);  // for demo purposes only
    return Promise.reject(error.message || error);
  }

  getHero(id: number): Promise<Hero> {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get(url)
               .toPromise()
               .then(response => response.json().data as Hero)
               .catch(this.handleError);
  }

  update(hero: Hero): Promise<Hero> {
    const url = `${this.heroesUrl}/${hero.id}`;
    return this.http
       .put(url, JSON.stringify(hero), {headers: this.headers})
       .toPromise()
       .then(() => hero)
       .catch(this.handleError);
  }

}


続いて、「hero-detail.component.html」に、<button>タグを追加。

<div *ngIf="hero">
  <h2>{{hero.name}} details!</h2>
  <div>
    <label>id: </label>{{hero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="hero.name" placeholder="name" />
  </div>
  <button (click)="goBack()">Back</button>
  <button (click)="save()">Save</button>
</div>

「hero-detail.component.ts」にsave()メソッドを追加します。

import 'rxjs/add/operator/switchMap';
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location } from '@angular/common';

import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({
  selector: 'hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: ['./hero-detail.component.css']
})
export class HeroDetailComponent implements OnInit {
  @Input() hero: Hero;

  constructor(
    private heroService: HeroService,
    private route: ActivatedRoute,
    private location: Location
  ) {}

  ngOnInit(): void {
      this.route.paramMap
        .switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
        .subscribe(hero => this.hero = hero);
  }

  goBack(): void {
    this.location.back();
  }

  save(): void {
    this.heroService.update(this.hero)
        .then(() => this.goBack());
  }

}
    

ブラウザで確認。

f:id:ts0818:20170917202336j:plain

試しに、変更して「Save」ボタンを押してみます。

f:id:ts0818:20170917202836j:plain

変更が反映されています。

f:id:ts0818:20170917202947j:plain

 

Add the ability to add heroes

heroes.component.html 」に追加します。

import 'rxjs/add/operator/switchMap';
<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes"
    [class.selected]="hero === selectedHero"
    (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
<div *ngIf="selectedHero">
  <h2>
    {{selectedHero.name | uppercase}} is my hero
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>
<div>
  <label>Hero name:</label> <input #heroName />
  <button (click)="add(heroName.value); heroName.value=''">Add</button>
</div>

heroes.component.ts 」にadd()メソッドを追加します

import 'rxjs/add/operator/switchMap';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { Hero } from './hero';
import { HeroService } from './hero.service';


@Component({
  selector: 'my-heroes',
  // templateUrl: './app.component.html',
  // styleUrls: ['./app.component.css'],
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
  // providers: [HeroService]
})
export class HeroesComponent implements OnInit {
  // title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;

  constructor(
    private router: Router,
    private heroService: HeroService) {}

  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit(): void {
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }

  add(name: String): void {
    name = name.trim();
    if(!name) { return; }
    this.heroService.create(name)
        .then(hero => {
          this.heroes.push(hero);
          this.selectedHero = null;
    });
  }
}

「hero.service.ts」にcreate()メソッドを追加

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';

@Injectable()
export class HeroService {

  private heroesUrl = 'api/heroes';  // URL to web api
  private headers = new Headers({'Content-Type': 'application/json'});

  constructor(private http: Http) {}

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
               .toPromise()
               .then(response => response.json().data as Hero[])
               .catch(this.handleError);
  }

  private handleError(error: any): Promise {
    console.error('An error occurred', error);  // for demo purposes only
    return Promise.reject(error.message || error);
  }

  getHero(id: number): Promise {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get(url)
               .toPromise()
               .then(response => response.json().data as Hero)
               .catch(this.handleError);
  }

  update(hero: Hero): Promise {
    const url = `${this.heroesUrl}/${hero.id}`;
    return this.http
       .put(url, JSON.stringify(hero), {headers: this.headers})
       .toPromise()
       .then(() => hero)
       .catch(this.handleError);
  }

  create(name: string): Promise {
    return this.http
      .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
      .toPromise()
      .then(res => res.json().data as Hero)
      .catch(this.handleError);
  }

}

ブラウザで確認。

f:id:ts0818:20170917205741j:plain

「Add」を押してみる。

f:id:ts0818:20170917205819j:plain

追加されました。

Add the ability to delete a hero

削除ボタンを追加します。「heroes.component.html」の

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes"
    [class.selected]="hero === selectedHero"
    (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span>
    <span>{{hero.name}}</span>
    <button class="delete" (click)="delete(hero); $event.stopPropagation()">×</button>
  </li>
</ul>
<div *ngIf="selectedHero">
  <h2>
    {{selectedHero.name | uppercase}} is my hero
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>
<div>
  <label>Hero name:</label> <input #heroName />
  <button (click)="add(heroName.value); heroName.value=''">Add</button>
</div>

heroes.component.ts」にdelete()メソッドを追加します。

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

import { Hero } from './hero';
import { HeroService } from './hero.service';


@Component({
  selector: 'my-heroes',
  // templateUrl: './app.component.html',
  // styleUrls: ['./app.component.css'],
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
  // providers: [HeroService]
})
export class HeroesComponent implements OnInit {
  // title = 'Tour of Heroes';
  heroes: Hero[];
  selectedHero: Hero;

  constructor(
    private router: Router,
    private heroService: HeroService) {}

  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit(): void {
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }

  add(name: string): void {
    name = name.trim();
    if (!name) { return; }
    this.heroService.create(name)
        .then(hero => {
          this.heroes.push(hero);
          this.selectedHero = null;
    });
  }

  delete(hero: Hero): void {
    this.heroService
        .delete(hero.id)
        .then(() => {
          this.heroes = this.heroes.filter(h => h !== hero);
          if (this.selectedHero === hero) { this.selectedHero = null; }
        });
  }
}
    

「hero.service.ts」にdelete()メソッドを追加します。

import { Injectable } from '@angular/core';
import { Headers, Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

import { Hero } from './hero';

@Injectable()
export class HeroService {

  private heroesUrl = 'api/heroes';  // URL to web api
  private headers = new Headers({'Content-Type': 'application/json'});

  constructor(private http: Http) {}

  getHeroes(): Promise<Hero[]> {
    return this.http.get(this.heroesUrl)
               .toPromise()
               .then(response => response.json().data as Hero[])
               .catch(this.handleError);
  }

  private handleError(error: any): Promise {
    console.error('An error occurred', error);  // for demo purposes only
    return Promise.reject(error.message || error);
  }

  getHero(id: number): Promise {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get(url)
               .toPromise()
               .then(response => response.json().data as Hero)
               .catch(this.handleError);
  }

  update(hero: Hero): Promise {
    const url = `${this.heroesUrl}/${hero.id}`;
    return this.http
       .put(url, JSON.stringify(hero), {headers: this.headers})
       .toPromise()
       .then(() => hero)
       .catch(this.handleError);
  }

  create(name: string): Promise {
    return this.http
      .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers})
      .toPromise()
      .then(res => res.json().data as Hero)
      .catch(this.handleError);
  }

  delete(id: number): Promise {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.delete(url, {headers: this.headers})
      .toPromise()
      .then(() => null)
      .catch(this.handleError);
  }

}

heroes.component.css」に追加します。

button.delete {
  float:right;
  margin-top: 2px;
  margin-right: .8em;
  background-color: gray !important;
  color:white;
}

ブラウザで確認。

f:id:ts0818:20170917211632j:plain

削除ができます。 

f:id:ts0818:20170917211653j:plain

 

Add the ability to search by name

検索機能を追加します。「angular-tour-of-heroes」>「src」>「app」を選択した状態で右クリックし、「新規(W)」>「その他(O)...」

f:id:ts0818:20170917212146j:plain

「TypeScript」>「TypeScript source File」を選択し、「次へ(N)>」をクリック。

f:id:ts0818:20170917212419j:plain

「ファイル名(F):」を「hero-search.service.ts」とし「完了(F)」。

f:id:ts0818:20170917212120p:plain

 

import { Injectable } from '@angular/core';
import { Http }       from '@angular/http';
 
import { Observable }     from 'rxjs/Observable';
import 'rxjs/add/operator/map';
 
import { Hero }           from './hero';
 
@Injectable()
export class HeroSearchService {
 
  constructor(private http: Http) {}
 
  search(term: string): Observable<Hero[]> {
    return this.http
               .get(`api/heroes/?name=${term}`)
               .map(response => response.json().data as Hero[]);
  }
}

続いて、「hero-search.component.html」を作成。「angular-tour-of-heroes」>「src」>「app」を選択した状態で右クリックし、「新規(W)」>「その他(O)...」

f:id:ts0818:20170917213232j:plain

「Web」>「HTMLファイル」を選択。

f:id:ts0818:20170917213254j:plain

「ファイル名(F):」を「hero-search.component.html」とし「次へ(N)>」。

f:id:ts0818:20170917213414j:plain

「完了(F)」。

f:id:ts0818:20170917213504j:plain

<div id="search-component">
  <h4>Hero Search</h4>
  <input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
  <div>
    <div *ngFor="let hero of heroes | async"
         (click)="gotoDetail(hero)" class="search-result" >
      {{hero.name}}
    </div>
  </div>
</div>

続いて、「hero-search.component.css」。

「hero-search.component.css」を作成。「angular-tour-of-heroes」>「src」>「app」を選択した状態で右クリックし、「新規(W)」>「その他(O)...」

f:id:ts0818:20170917214642j:plain

「Web」>「CSSファイル」を選択。

f:id:ts0818:20170917214659j:plain

「ファイル名(F):」を「hero-search.component.css」とし「次へ(N)>」。

f:id:ts0818:20170917214730j:plain

「完了(F)」。

f:id:ts0818:20170917215104j:plain

.search-result{
  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;
}
 
.search-result:hover {
  color: #eee;
  background-color: #607D8B;
}
 
#search-box{
  width: 200px;
  height: 20px;
}    

「hero-search.component.ts」。

「hero-search.component.ts」を作成。「angular-tour-of-heroes」>「src」>「app」を選択した状態で右クリックし、「新規(W)」>「その他(O)...」

f:id:ts0818:20170917215150j:plain

「TypeScript」>「TypeScript source File」を選択し、「次へ(N)>」をクリック。

f:id:ts0818:20170917215213j:plain

「ファイル名(F):」を「hero-search.component.ts」とし「完了(F)」。

f:id:ts0818:20170917215250j:plain

 

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

import { Observable }        from 'rxjs/Observable';
import { Subject }           from 'rxjs/Subject';

// Observable class extensions
import 'rxjs/add/observable/of';

// Observable operators
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';

import { HeroSearchService } from './hero-search.service';
import { Hero } from './hero';

@Component({
  selector: 'hero-search',
  templateUrl: './hero-search.component.html',
  styleUrls: [ './hero-search.component.css' ],
  providers: [HeroSearchService]
})
export class HeroSearchComponent implements OnInit {
  heroes: Observable<Hero[]>;
  private searchTerms = new Subject<string>();

  constructor(
    private heroSearchService: HeroSearchService,
    private router: Router) {}

  // Push a search term into the observable stream.
  search(term: string): void {
    this.searchTerms.next(term);
  }

  ngOnInit(): void {
    this.heroes = this.searchTerms
      .debounceTime(300)        // wait 300ms after each keystroke before considering the term
      .distinctUntilChanged()   // ignore if next search term is same as previous
      .switchMap(term => term   // switch to new observable each time the term changes
        // return the http search observable
        ? this.heroSearchService.search(term)
        // or the observable of empty heroes if there was no search term
        : Observable.of<Hero[]>([]))
      .catch(error => {
        // TODO: add real error handling
        console.log(error);
        return Observable.of<Hero[]>([]);
      });
  }

  gotoDetail(hero: Hero): void {
    let link = ['/detail', hero.id];
    this.router.navigate(link);
  }
}

エラー!

f:id:ts0818:20170917215447j:plain

「Identifier 'link' is never reassinged; use 'const' instead of 'let'」と言ってます。

f:id:ts0818:20170917215655j:plain

コードをよく見ると、

  gotoDetail(hero: Hero): void {
    let link = ['/detail', hero.id];
    this.router.navigate(link);
  }

「['/detail', hero.id]」が変数として認識されていませんね...致し方ないですが、「Jump to rule configuration」を選択して、「 "prefer-const": true, 」を消します。

{
  "rulesDirectory": [
    "node_modules/codelyzer"
  ],
  "rules": {
    "arrow-return-shorthand": true,
    "callable-types": true,
    "class-name": true,
    "comment-format": [
      true,
      "check-space"
    ],
    "curly": true,
    "eofline": true,
    "forin": true,
    "import-blacklist": [
      true,
      "rxjs"
    ],
    "import-spacing": false,
    "indent": [
      true,
      "spaces"
    ],
    "interface-over-type-literal": true,
    "label-position": true,
    "max-line-length": [
      true,
      140
    ],
    "member-access": false,
    "member-ordering": [
      true,
      {
        "order": [
          "static-field",
          "instance-field",
          "static-method",
          "instance-method"
        ]
      }
    ],
    "no-arg": true,
    "no-bitwise": true,
    "no-console": [
      true,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-debugger": true,
    "no-duplicate-super": true,
    "no-empty": false,
    "no-empty-interface": true,
    "no-eval": true,
    "no-inferrable-types": [
      true,
      "ignore-params"
    ],
    "no-misused-new": true,
    "no-non-null-assertion": true,
    "no-shadowed-variable": true,
    "no-string-literal": false,
    "no-string-throw": true,
    "no-switch-case-fall-through": true,
    "no-trailing-whitespace": true,
    "no-unnecessary-initializer": true,
    "no-unused-expression": true,
    "no-use-before-declare": true,
    "no-var-keyword": true,
    "object-literal-sort-keys": false,
    "one-line": [
      true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-whitespace"
    ],
    "quotemark": [
      true,
      "single"
    ],
    "radix": true,
    "semicolon": [
      true,
      "always"
    ],
    "triple-equals": [
      true,
      "allow-null-check"
    ],
    "typedef-whitespace": [
      true,
      {
        "call-signature": "nospace",
        "index-signature": "nospace",
        "parameter": "nospace",
        "property-declaration": "nospace",
        "variable-declaration": "nospace"
      }
    ],
    "typeof-compare": true,
    "unified-signatures": true,
    "variable-name": false,
    "whitespace": [
      true,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator",
      "check-type"
    ],
    "directive-selector": [
      true,
      "attribute",
      ["app", "my*", "hero*"],
      "camelCase"
    ],
    "component-selector": [
      true,
      "element",
      ["app", "my*", "hero*"],
      "kebab-case"
    ],
    "use-input-property-decorator": true,
    "use-output-property-decorator": true,
    "use-host-property-decorator": true,
    "no-input-rename": true,
    "no-output-rename": true,
    "use-life-cycle-interface": true,
    "use-pipe-transform-interface": true,
    "component-class-suffix": true,
    "directive-class-suffix": true,
    "no-access-missing-member": true,
    "templates-use-public": true,
    "invoke-injectable": true
  }
}

保存。エラーは消えたけど...「Angular DOC」の見本のソースの「tslint.json」の設定でも「 "prefer-const": true, 」削除されてましたし、ドンマイ。

f:id:ts0818:20170917222547j:plain

 

 

Add the search component to the dashboard

検索機能を表示できるように「dashboard.component.html」に追加。

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

「app.module.ts 」で「HeroSearchComponent」をインポートします。

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule }   from '@angular/forms';
import { HttpModule }    from '@angular/http';  // 追加

// Imports for loading & configuring the in-memory web api
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService }  from './in-memory-data.service';

import { AppComponent }        from './app.component';
import { DashboardComponent }  from './dashboard.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent }     from './heroes.component';
import { HeroService }         from './hero.service';

import { AppRoutingModule }    from './app-routing.module';
import { HeroSearchComponent } from './hero-search.component'; // 追加

@NgModule({
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent,
    HeroSearchComponent  // 追加
  ],
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    HttpModule, // 追加
    InMemoryWebApiModule.forRoot(InMemoryDataService),  // 追加
    AppRoutingModule
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

一応、コマンドプロンプト側にて「Ctrl + C」でAngularJSの開発用サーバーを一旦、停止し「npm start」で再起動しておきます。

f:id:ts0818:20170917224323j:plain

ブラウザで確認。

f:id:ts0818:20170917224457j:plain

検索も表示できました。

f:id:ts0818:20170917225427j:plain

検索結果も大丈夫そうです。

f:id:ts0818:20170917225439j:plain

ちなみに、html系のファイルに警告が出てしまっていますが、Eclipseになんかプラグインとか入れないとダメかもです。(「Angular IDE 2017 CI8」とかが面倒みてくれると思ったのですが)

エラーではないのですが、しばらくはこんな感じで警告が出てしまうことになりますかね。

f:id:ts0818:20170918154823j:plain

 

むちゃくちゃながくなってしまいましたが、「tslint.json」とかの設定はAngular DOC live example / download exampleでソースをダウンロードしてきたほうが良さそうです。

設定まわりで止まるのって本当に調べるので時間が取られて疲れますね...。

⇩  そして、なにやらAngularJSの評判が芳しくないのが気になりますね...

Angular 2/4が狭量で遅すぎる理由 | プロダクト・サービス | POSTD