Dart2×AngularDart5 チュートリアル Part3. Multiple Components/Services

この記事は「Webアプリ入門しようよ! in AngularDart」の続きです。

目次
Part1:The Hero Editor
Part2:Master/Detail
Part3:Multiple Components/Services ←イマココ!
Part4:Routing
Part5:HTTP

参考

さっそくPart2も終わったようですね!素晴らしい!

え?終わってない?
そんなあなたにはこちら↓を差し上げましょう。
Part2終了時点のソースコード

それでは続きから!Fight!!(ゴングが鳴る)

Heroをコンポーネント化!

現状、Heroリストと選択したHeroの詳細はAppComponentで表示しています。
が、再利用性を高めるために、HeroリストとHeroの詳細表示は分離した方がよいでしょう。

とりあえずHeroの詳細表示をAppComponentから分離しましょう!

  1. src/hero_component.dartを作る!

    import 'package:angular/angular.dart';
    import 'package:angular_forms/angular_forms.dart';
    
    import 'hero.dart';
    
    @Component(
      selector: 'my-hero',
      templateUrl: 'hero_component.html',
      directives: [coreDirectives, formDirectives],
    )
    class HeroComponent {
      // ↓他のコンポーネントと値を紐付けるには、`@Input`プロパティを追加する!
      @Input()
      Hero hero;
    }
    
  2. src/hero_component.htmlを作る!

    <!-- ↓`app_component.html`からコピペ! -->
    <!-- ↓selected→heroに修正!  -->
    <div *ngIf="hero != null">
        <h2>{{hero.name}}</h2>
        <div><label>id: </label>{{hero.id}}</div>
        <div>
            <label>name: </label>
            <input [(ngModel)]="hero.name" placeholder="name">
        </div>
    </div>
    
  3. app_component.dartを修正する!

    import 'package:angular/angular.dart';
    // angular_from.dartは使わないので削除
    
    import 'src/hero.dart';
    import 'src/mock_heroes.dart';
    
    // ↓追加!
    import 'src/hero_component.dart';
    
    @Component(
      selector: 'my-app',
      styleUrls: ['app_component.css'],
      templateUrl: 'app_component.html',
      // ↓fromDirectiveの代わりにHeroComponentを追加!
      directives: [coreDirectives, HeroComponent],
    )
    
  4. app_component.htmlを修正する!

    <h1>{{title}}</h1>
    <!-- ↓HeroComponentを使うように修正! -->
    <my-hero [hero]="selected"></my-hero>
    <h2>Heroes</h2>
    <ul class="heroes">
        <!-- heroes内の要素数分繰り返す! -->
         <li *ngFor="let hero of heroes" [class.selected]="hero === selected" (click)="onSelect(hero)">
             <!-- heroを表示する! -->
             <span class="badge">{{hero.id}}</span> {{hero.name}}
         </li>
     </ul>
    

ブラウザを更新して確認!
同じように表示されたらOK!

ネクストッ!

Heroをサービス化!

現状、AppComponentではheroesを直接モックで初期化していますが、Heroのデータは今後色々なコンポーネントから参照する可能性が高いので、AppComponentとHeroのデータへアクセスする関数は分離したほうが良いでしょう。

Heroのデータへアクセスする関数はHeroServiceにまとめます!

  1. src/hero_service.dartを作る!

    import 'hero.dart';
    import 'mock_heroes.dart';
    
    class HeroService {
      // ↓全てのHeroを取得する関数を実装!
      // ↓(今はまだモックだけど...)
      List<Hero> getAll() => mockHeroes;
    }
    
  2. HeroServiceを使うようにapp_component.dartを修正!

    import 'package:angular/angular.dart';
    
    import 'src/hero.dart';
    
    import 'src/hero_component.dart';
    // mock_heroesは不要になったので削除!
    // ↓代わりにHeroServiceを使う!
    import 'src/hero_service.dart';
    
    @Component(
      selector: 'my-app',
      styleUrls: ['app_component.css'],
      templateUrl: 'app_component.html',
      directives: [coreDirectives, HeroComponent],
      // ↓AppComponent内でHeroServiceを使うために追加!
      providers: [ClassProvider(HeroService)],
    )
    // ↓初期化時にHeroリストを取得したいのでOnInitを追加!
    class AppComponent implements OnInit {
      final title = 'Tour of Heroes';
      // ↓heroesはngOnInit内で初期化するのでここでは初期化しない。
      List<Hero> heroes;
      Hero selected;
    
      // ↓HeroServiceを使うために追加!
      final HeroService _heroService;
    
      AppComponent(this._heroService);
    
      void onSelect(Hero hero) => selected = hero;
    
      // ↓初期化時にHeroリストを取得!
      void ngOnInit() => _getHeroes();
    
      void _getHeroes() {
        heroes = _heroService.getAll();
      }
    }
    

ブラウザを更新して確認!
同じように表示されたらOK!

ネクストッ!

HeroServiceを非同期化!

将来的には、getAll()でサーバーからHeroのデータを取得しますが、その通信中に他の処理を待たせてはいけません。

HeroServiceの処理を非同期化することで、他の処理を待たせずに済みます!

  1. hero_service.dartを修正する!

    // ↓非同期処理を使うならこれをインポート!
    import 'dart:async';
    
    import 'hero.dart';
    import 'mock_heroes.dart';
    
    class HeroService {
      // ↓これが非同期処理の書き方だ!(投げやり)
      Future<List<Hero>> getAll() async => mockHeroes;
    }
    
  2. app_component.dartを修正する!

    // ↓追加!
    import 'dart:async';
    
    // …
    
    class AppComponent implements OnInit {
    
      // … 
    
      // ↓非同期処理の書き方に変える!
      Future<void> _getHeroes() async {
        // ↓非同期な関数を呼ぶときは、awaitを付ける!
        heroes = await _heroService.getAll();
      }
    }
    

ブラウザを更新して確認!
同じように表示されたらOK!

あえて遅延させてみる

今はサーバーでなくmockHeroesを渡しているだけなので非同期化する前と動作はほぼ同じ!
非同期になっていることを確かめたかったら、以下のようにgetAll()を修正してみよう!

Future<List<Hero>> getAll() async {
  return Future.delayed(Duration(seconds: 2), () => mockHeroes);
}

ブラウザを更新してタイトルが表示されてから、2秒後にリストが表示されるはず!

気になる人は試してみてください!

ネクストッ!

HeroListをコンポーネント化!

AppComponentが徐々にシンプルになってきました!
この調子でHeroListもAppComponentから分離しましょう!

  1. src/hero_list_component.dartを作成!

    • app_component.dartの内容をコピペ!
    • app_componenthero_list_componentに名前を修正!
    import 'dart:async';
    import 'package:angular/angular.dart';
    
    import 'hero.dart';
    import 'hero_component.dart';
    import 'hero_service.dart';
    
    @Component(
      selector: 'my-heroes',
      // ↓修正!
      styleUrls: ['hero_list_component.css'],
      templateUrl: 'hero_list_component.html',
      directives: [coreDirectives, HeroComponent],
      providers: [ClassProvider(HeroService)],
    )
    class HeroListComponent implements OnInit {
      final title = 'Tour of Heroes';
      final HeroService _heroService;
      List<Hero> heroes;
      Hero selected;
    
      // ↓修正!
      HeroListComponent(this._heroService);
    
      void onSelect(Hero hero) => selected = hero;
    
      void ngOnInit() => _getHeroes();
    
      Future<void> _getHeroes() async {
        heroes = await _heroService.getAll();
      }
    }
    
  2. src/hero_list_component.htmlを作成!

    • app_component.htmlのうち、Heroに関係する部分(タイトル以外)をコピペ!
    <my-hero [hero]="selected"></my-hero>
    
    <h2>Heroes</h2>
    <ul class="heroes">
        <!-- heroes内の要素数分繰り返す! -->
        <li *ngFor="let hero of heroes" [class.selected]="hero === selected" (click)="onSelect(hero)">
            <!-- heroを表示する! -->
            <span class="badge">{{hero.id}}</span> {{hero.name}}
        </li>
    </ul>
    
  3. src/hero_list_component.cssを作成!

    • app_component.cssの内容を全部コピペ!
    • app_component.cssは空にしちゃおう!
  4. HeroListComponentを使うようにapp_component.dartを修正!

    • HeroListComponentに移動したコードは削除!
    import 'package:angular/angular.dart';
    
    // ↓追加!
    import 'src/hero_list_component.dart';
    
    @Component(
      selector: 'my-app',
      styleUrls: ['app_component.css'],
      templateUrl: 'app_component.html',
      // ↓変更!
      directives: [HeroListComponent],
    )
    class AppComponent {
      final title = 'Tour of Heroes';
    }
    
  5. HeroListComponentを使うようにapp_component.htmlを修正!

    • HeroListComponentに移動したコードは削除!
    <h1>{{title}}</h1>
    <!-- ↓追加! -->
    <my-heroes></my-heroes>
    

ブラウザを更新して確認!
同じように表示されたらOK!

ひと休み

ここまでいかがだったでしょうか。

今回は一切画面の変化がなく退屈だったかもしれません。

でも安心してください。これから楽しくなります!
ボリュームも増えて説明も雑になりますけどね←

ネクストッ! → Part4:Routing