前回は、複数ページを作ってページ遷移を試しながら、ルーティングの基礎を学びました。
本記事では、
- 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)
の要素をもたせます。
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
で実際に表示してみましょう。
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が出来た。');
}
}
<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>
.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
にアクセスします。
このように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で商品情報を取得するまでの表示)
<div class="container">
<div class="title">商品一覧</div>
<div class="product-list-container">
<div class="waiting-for-products">
商品を取得しています...
</div>
</div>
</div>
.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
にアクセス)
データを取得したら別の表示になるようにする
次に、APIアクセスが終わり商品情報が取得できたと仮定して(今はまだAPIアクセスはしません)、商品情報が取得できる前と後とで表示を変更してみましょう。
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);
}
}
<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>
.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
にアクセス)
解説
product-list.component.ts
ngOnInit()
で、 setTimeout
を使って、 3秒後に products
にデータが入るようにしています。
product-list.component.html
商品取得中
, 商品取得後
の表示を分けるために、 *ngIf
というディレクティブを利用しています。
<div class="waiting-for-products" *ngIf="products === null; else productList">
中身は条件式になっていて、 products
が null
であればこの div
を表示する。それ以外の場合は、 productList
で示された要素を持っているところを表示する、という意味になります。
product-list.component.ts
で コンポーネント初期化の3秒後に products
が null
でなくなるので、最初の3秒間だけ表示されています。
<ng-template #productList>
*ngIf
の else productList
の部分で指定しているのは上記部分になります。
この else
の部分は、 ng-template
という要素を使ってください。 また、その中に else
で示された productList
を #
付きで指定します。
することで、3秒後にこの要素が表示されるようになります。
TIPS: ディレクティブとは
ディレクティブというのは、HTMLにおける属性(attribute)のようなもので、
コンポーネントに対して特定の作用を付与できるモジュールのようなものと思うと良いかと思います。
私は、src
とsrcset
を書くのが面倒なので一つ書くだけで両方書いてくれるディレクティブ とか作ったりしてます
実際のコミット
Angularのディレクティブ基礎( *ngFor によるHTML内でのループ )
上記例では、3つ存在する商品全てを HTML の中でそれぞれ記載しました。
ただ、このやり方では、商品の数が決まっていないとダメですし、なにより面倒です。
Angularには、 HTMLタグを繰り返し(ループ)記述したいためのディレクティブ *ngFor
があります。
これを使ってコードを書き換えてみます。
<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>
のように、すると、i
にindex
が、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