38
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Angular入門 未経験から1ヶ月でサービス作れるようにする その4. モデルとループと分岐と

Last updated at Posted at 2018-07-26

前回は、複数ページを作ってページ遷移を試しながら、ルーティングの基礎を学びました。

本記事では、

  • Angularでのデータ管理の基礎
    • モデルクラスの作成
  • Angularのディレクティブ基礎
    • HTMLでの分岐
    • HTMLでのループ

をやっていきます。

この記事のソースコード

https://github.com/seteen/AngularGuides/tree/入門その04

Angularでのデータ管理の基礎

前回、 product-list.component , product-detail.component を作成しました。
前回までは特に名前に意味がなかったのですが、今回から意味を加えたいと思います。

コンポーネント名 役割
product-list.component 商品リストページ。商品の一覧を表示する画面
product-detail.component 商品詳細ページ。商品一覧でクリックされた商品の詳細を表示する画面

これから各ページの内容を作成していきますが、これらのページを作るときに重要なのは、 商品(product) とはどのようなものなのかを各コンポーネントが共通認識として理解できるようにすることです。

Angularプロジェクト内での共通認識を行うために、モデルのファイルを作成します。
プロジェクト内で共通で使うファイルなので、

src/app/shared/models

に保存します。

TIPS: ファイルの配置
Angularとして、各ファイルをどこに置かないといけない、という制約はありません。
プロジェクトに関わっているエンジニア内でここが良い、と思う場所に置くのが良いと思います。
Angular公式のガイドラインもあるので参考にしてください。
https://angular.io/guide/styleguide#overall-structural-guidelines

product.ts モデルの作成

商品(product) には、 一意に識別できるID(id) , 名前(name) , 価格(price) , 説明(description) の要素をもたせます。

src/app/shared/models/product.ts
export class Product {
  id: number;
  name: string;
  price: number;
  description: string;

  constructor(id, name, price, description) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.description = description;
  }
}

このように、共通で使えるファイルに 商品(product) モデルを定義することで、このプロジェクト内での商品の共通認識が作れました。
Typescript なのでJavascriptしか知らない方には書き方に違和感があると思います。 Typescript の書き方などは、次回解説していきます。
今回は、なんとなくこういうものなんだと思っておいてください。

コンポーネントからモデルを利用する

Productモデルを作成したので、 product-detail.component.ts で実際に表示してみましょう。

src/app/product/product-detail/product-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { Product } from '../../shared/models/product';

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html',
  styleUrls: ['./product-detail.component.scss'],
})
export class ProductDetailComponent implements OnInit {
  product: Product;

  constructor() { }

  ngOnInit() {
    this.product = new Product(1, 'Angular入門書「天地創造の章」', 3800, '神は云った。「Angularあれ」。するとAngularが出来た。');
  }

}
src/app/product/product-detail/product-detail.component.html
<div class="container">
  <div class="title">商品詳細</div>
  <div class="product-detail-container">
    <div class="param-line">
      <div class="label">ID</div>
      <div class="value">{{ product.id }}</div>
    </div>
    <div class="param-line">
      <div class="label">名前</div>
      <div class="value">{{ product.name }}</div>
    </div>
    <div class="param-line">
      <div class="label">価格</div>
      <div class="value">{{ product.price }}</div>
    </div>
    <div class="param-line">
      <div class="label">説明</div>
      <div class="value">{{ product.description }}</div>
    </div>
  </div>
  <div class="footer">
    <div class="button black" routerLink="/products">商品一覧へ</div>
  </div>
</div>
src/app/product/product-detail/product-detail.component.scss
.container {
  margin: auto;
  padding: 32px 0;
  width: 800px;
  .title {
    padding: 8px 0;
    text-align: center;
    width: 100%;
    font-weight: 600;
    font-size: 18px;
  }

  .product-detail-container {
    padding-left: 48px;
    border: 1px solid #D9DBDE;
    border-radius: 4px;
    background-color: #FFFFFF;

    .param-line {
      display: flex;
      padding: 16px 8px;

      &:nth-child(n+2) {
        border-top: 1px solid #D9DBDE;
      }

      .label {
        width: 100px;
      }
    }
  }

  .footer {
    display: flex;
    justify-content: ce;
    margin-top: 20px;
  }
}

上記を修正し、 ng serve で起動し、 http://localhost:4200/products/test にアクセスします。

001.png

このようにProductの情報が画面に表示されているはずです。

解説

HTML, CSS が少し複雑になりましたが、基本的には、 入門その2 で app.component.ts を変更したときと同様のことをしています。

product-detail.component.ts

Angular CLI のコンポーネントの作成で、作成されたファイルに、最初から ngOnInit() というメソッドがありました。
まだ解説していませんでしたので、解説します。

Angularのコンポーネントには、ライフサイクルのイベントフックが用意されています。
つまり、コンポーネントができてから、なくなるまでのイベントがいくつか用意されていて、 Typescript のメソッド名が決まっているということです。

例えば、 コンポーネントができたときに実行したい処理 , コンポーネントが描画されたら実行したい処理 などの処理を各メソッドが決まっているので、
用途にあったメソッドをうまく使って表示の調整をしたりできます。

その中でも最も利用するのが、 ngOnInit() です。 ng というのは A ng ular の ng を取っているものなので、 ng + OnInit つまり、初期化後に呼び出されるメソッドになります。

constructor と何が違うんだ、と思うかもしれませんが、今のところはそのあたりは気にせず、 ngOnInit() に書いておいた方が予想外の動きをせずに安全なので初期化処理はこちらに書いておきましょう。
(最初の学習ではとりあえずやってみる、が大切です。やってるうちにだいたいわかってきます)

TIPS: ライフサイクルのフック
他にもどのような種類のフックがあるのかは、Angularの公式ページを参照してみてください。
https://angular.io/guide/lifecycle-hooks

product-detail.component.html

Productの各要素を {{}} を使って表示しています。 {{}} の中で使えるのは、関連している Typescript のコンポーネントのクラスの public なインスタンス変数とメソッドです。
HTMLで使いたい値があれば、インスタンス変数かメソッドにしておきましょう( WebStorm があれば補完で出てくるので何が使えるかがすぐわかります )。

product-detail.component.scss

見た目を最低限良い感じにするための最小限のコードです。

コンポーネント間でCSSがわかれるので、自由にクラス名を定義して書いてます。

Angularの入門なので、特に内容には触れませんが、 私は display: flex がとても好きです。

実際のコミット

Angularのディレクティブ基礎( *ngIf によるHTML内での分岐 )

今度は 商品リストページ ( product-list.component ) に移ります。

商品リストを作っていく前に、商品を取得している間の表示を作成します。(APIで商品情報を取得するまでの表示)

src/app/product/product-list/product-list.component.html
<div class="container">
  <div class="title">商品一覧</div>
  <div class="product-list-container">
    <div class="waiting-for-products">
      商品を取得しています...
    </div>
  </div>
</div>
src/app/product/product-list/product-list.component.scss
.container {
  margin: auto;
  padding: 32px 0;
  width: 800px;
  .title {
    padding: 8px 0;
    text-align: center;
    width: 100%;
    font-weight: 600;
    font-size: 18px;
  }
}

HTMLとSCSSしか変更していません。
現状の状態で、画面を見てみましょう( ng serve -> http://localhost:4200/products にアクセス)

002.png

データを取得したら別の表示になるようにする

次に、APIアクセスが終わり商品情報が取得できたと仮定して(今はまだAPIアクセスはしません)、商品情報が取得できる前と後とで表示を変更してみましょう。

src/app/product/product-list/product-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Product } from '../../shared/models/product';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
  products: Product[] = null;

  constructor() { }

  ngOnInit() {
    setTimeout(() => {
      this.products = [
        new Product(1, 'Angular入門書「天地創造の章」', 3800, '神は云った。「Angularあれ」。するとAngularが出来た。'),
        new Product(2, 'Angularを覚えたら、年収も上がって、女の子にももてて、人生が変わりました!', 410, '年収300万のSEが、Angularと出会う。それは、小さな会社の社畜が始めた、最初の抵抗だった。'),
        new Product(3, '異世界転生から始めるAngular生活(1)', 680,
          'スパゲッティの沼でデスマーチ真っ最中の田中。過酷な日々からの現実逃避か彼は、異世界に放り出され、そこでAngularの入門書を拾う。現実逃避でさえ、プログラミングをするしかない彼に待ち受けるのは!?'),
      ];
    }, 3000);
  }

}
src/app/product/product-list/product-list.component.html
<div class="container">
  <div class="title">商品一覧</div>
  <div class="product-list-container">
    <div class="waiting-for-products" *ngIf="products === null; else productList">
      商品を取得しています...
    </div>
    <ng-template #productList>
      <div class="product-list-table">
        <div class="product-line header">
          <div class="product-id">ID</div>
          <div class="product-name">名前</div>
          <div class="product-price">価格</div>
        </div>
        <div class="product-line">
          <div class="product-id">{{ products[0].id }} </div>
          <div class="product-name">{{ products[0].name }}</div>
          <div class="product-price">{{ products[0].price }}</div>
        </div>
        <div class="product-line">
          <div class="product-id">{{ products[1].id }} </div>
          <div class="product-name">{{ products[1].name }}</div>
          <div class="product-price">{{ products[1].price }}</div>
        </div>
        <div class="product-line">
          <div class="product-id">{{ products[2].id }} </div>
          <div class="product-name">{{ products[2].name }}</div>
          <div class="product-price">{{ products[2].price }}</div>
        </div>
      </div>
    </ng-template>
  </div>
</div>
src/app/product/product-list/product-list.component.scss
.container {
  margin: auto;
  padding: 32px 0;
  width: 800px;
  .title {
    padding: 8px 0;
    text-align: center;
    width: 100%;
    font-weight: 600;
    font-size: 18px;
  }

  .product-list-container {
    .product-list-table {
      border: 1px solid #D9DBDE;
      border-radius: 4px;
      overflow: hidden;

      .product-line {
        display: flex;
        padding: 16px 24px;

        &.header {
          background-color: #F5F5F5;
        }

        &:nth-child(n+2) {
          border-top: 1px solid #D9DBDE;
        }

        .product-id {
          width: 40px;
        }
        .product-name {
          width: 630px;
        }
      }
    }
  }
}

上記のように変更したら、画面を見てみましょう( ng serve -> http://localhost:4200/products にアクセス)

003.gif

解説

product-list.component.ts

ngOnInit() で、 setTimeout を使って、 3秒後に products にデータが入るようにしています。

product-list.component.html

商品取得中 , 商品取得後 の表示を分けるために、 *ngIf というディレクティブを利用しています。

.html
    <div class="waiting-for-products" *ngIf="products === null; else productList">

中身は条件式になっていて、 productsnull であればこの div を表示する。それ以外の場合は、 productList で示された要素を持っているところを表示する、という意味になります。
product-list.component.ts で コンポーネント初期化の3秒後に productsnull でなくなるので、最初の3秒間だけ表示されています。

.html
    <ng-template #productList>

*ngIfelse productList の部分で指定しているのは上記部分になります。
この else の部分は、 ng-template という要素を使ってください。 また、その中に else で示された productList# 付きで指定します。
することで、3秒後にこの要素が表示されるようになります。

TIPS: ディレクティブとは
ディレクティブというのは、HTMLにおける属性(attribute)のようなもので、
コンポーネントに対して特定の作用を付与できるモジュールのようなものと思うと良いかと思います。
私は、 srcsrcset を書くのが面倒なので一つ書くだけで両方書いてくれるディレクティブ とか作ったりしてます

実際のコミット

Angularのディレクティブ基礎( *ngFor によるHTML内でのループ )

上記例では、3つ存在する商品全てを HTML の中でそれぞれ記載しました。
ただ、このやり方では、商品の数が決まっていないとダメですし、なにより面倒です。

Angularには、 HTMLタグを繰り返し(ループ)記述したいためのディレクティブ *ngFor があります。

これを使ってコードを書き換えてみます。

src/app/product/product-list/product-list.component.html
<div class="container">
  <div class="title">商品一覧</div>
  <div class="product-list-container">
    <div class="waiting-for-products" *ngIf="products === null; else productList">
      商品を取得しています...
    </div>
    <ng-template #productList>
      <div class="product-list-table">
        <div class="product-line header">
          <div class="product-id">ID</div>
          <div class="product-name">名前</div>
          <div class="product-price">価格</div>
        </div>
        <div class="product-line" *ngFor="let product of products">
          <div class="product-id">{{ product.id }} </div>
          <div class="product-name">{{ product.name }}</div>
          <div class="product-price">{{ product.price }}</div>
        </div>
      </div>
    </ng-template>
  </div>
</div>

*ngFor の中身は、 let xxx of yyy と記載するのが基本です。これは、 yyy の 要素一つ一つを xxx という変数に入れてループする。というものです。
通常の Javascript にも存在している for 文の書き方とほとんど同じなのでわかりやすいと思います。

TIPS: ngFor の色々な変数
ngForでは、ループでよく使うインデックス、最初か最後かなどの情報が簡単に取得できるようになっています。
<li *ngFor="let product of products; let i = index; let f = first>
のように、すると、 iindex が、 f には最初の要素かどうかの boolean が入ります。便利ですね!
詳しくはこちら↓
https://angular.io/api/common/NgForOf#local-variables

ここまでのコミット

おまけ

*ngIf と同じようなもので、 ngSwitch というものがあります。

名前からわかるように、 switch 文が使えるものです。

使い方としては、

<div [ngSwitch]="state">
  <div *ngSwitchCase="1">One</div>
  <div *ngSwitchCase="2">Two</div>
  <div *ngSwitchDefault>Default</div>
</div>

のように使います。分岐が多いときは、こちらを使っても良いかもしれません。

まとめ

今回は、プロジェクト内でデータのやり取りをするためのモデルの作り方と、 *ngIf による条件分岐 , *ngFor によるループを学びました。
少しずつ、Angularのことがわかり始めてきたのではないでしょうか。

次回は、Typescriptの基礎的な部分の解説と、Angularにおいてデータ管理部分をコンポーネントから切り離す方法を解説していきます。
Angular入門 未経験から1ヶ月でサービス作れるようにする その5. サービスとTypescriptの基礎

入門記事一覧

「Angular入門 未経験から1ヶ月でサービス作れるようにする」は、記事数が多いため、まとめ記事 を作っています。
https://qiita.com/seteen/items/43908e33e08a39612a07

38
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?