はじめに
近年SPA(Single Page Application)のようなwebアプリケーションの開発で用いられているJavaScriptのフレームワークであるAngularの公式ドキュメントの(左タブ「はじめに」→「試してみよう」)を順に進めた記録を残す。要所に正確ではない表現もあるかもしれないが第一歩の理解のハードルを低くしていると認識いただきたい。私自身も初学者のためご理解を。
まずは「入門」から。
環境について
チュートリアルを進めるにあたってStackBlitzというサイトを用いる。StackBlitzはVSCodeのブラウザ版のようなもので、見た目がVSCodeとほぼ同一で使いやすく、かつブラウザ上でコンパイル、ビルドできる。そのため環境構築せずとも利用することができる。
公式サイトの最初に出てくるリンクはStackBlitzのトップページのため、プログラムソースが入っていない。なのでCreate the sample projectにあるリンク先にとぶ。こちらのソースプログラムが用意されたページで始め、このソースを編集していく形で進める。編集すると自動でコンパイルされ、ページに自動で反映される。これはAngularのホットリロード
という仕組みである。
下記のようなページからスタートとなる。右にはビルド後のページが表示されていて、ソースと並べてリアルタイムで編集の反映を確認できる。また右上の「Open in New Tab」でブラウザの他のタブで表示させることも可能である。
TypeScriptについて
TypeScriptについてと、JavaScriptとの違いについて簡単に説明する。
TypeScriptはJavaScriptの上位互換という認識で問題ない。
大きな違いについて2つほど説明する。
(1)型宣言が必要
Typeと名にあるように、JavaScriptでは必要なかった型宣言をする必要がある。
(2)コンパイルが必要
TypeScriptの実行はコンパイルが必要で、ここでいうコンパイルとはJavaScriptコードへ変換されることである。
(1)とも繋がるが、型指定、コンパイルというプロセスを経る必要があるという点でエラーを未然に防げるという特徴がある。なお、JSに変換されるということでJSコードはそのまま利用でき、上位互換と記述したのはそのためである。
AnguarはTypeScriptで書く必要がある。
Angularの基本構成について
そもそもAngularがどういったプログラム構成になっているか最初は全くわからないと思うのでこちらに軽くまとめる。「Angularとは」というページが最初に有るがこちらは読み飛ばしてページの動きを見ながら各ソースの理解を深めることをオススメする。当チュートリアルで出てくるAngularの構成単位のComponent、Module、Serviceについてここで説明する。途中わからなくなったらこちらに立ち返ると全体感を掴みつつ進めることができると思う。
-
Component
AngularはComponent単位で画面を作っていく。Componentは画面表示をするためのものという理解で良いと考える。データ等の受け渡しをするのみである。
〜サンプルアプリケーションの見学〜の欄に各Componentがどの範囲なのか示してあるのでそれを確認する。Componentの中はソースを見て確認できるが、HTMLファイル、CSSファイル、TypeScriptファイルで構成されている。 -
Module
Angularの各種設定を行うファイルがmoduleと認識していればまずは問題ないと思う。
${ファイル名.module.ts}となっており、この後編集することになるが、app.module.tsというファイルがルートモジュールと呼ばれるアプリ全体の設定を行うソースファイルである。
なお、このファイルにすべての設定を記述することもできるが、他のmoduleファイルを作成して単位ごとに設定をすることもそれをapp.module.tsと紐付けることも可能。 -
Service
Seviceはいわゆるビジネスロジック等の関数を実行するためのファイルという理解である。
上述した画面表示を担当するComponentとは役割が明らかに分かれている。
後に出てくるがDI(依存性の注入)の設定が必要なのもServiceである。DIの私のイメージとしては画面表示を行うコンポーネントでビジネスロジックを用いるために行うインスタンス化
である。なのでComponentではServie(class)のインスタンス化を行い、ビジネスロジック、つまりServiceでDIの設定が必要ということで十分である。
商品リストを作成する
前置きが長くなってしまったが、Angularとは、を理解する上で重要なので整理してほしい。
ここから実際にソースを編集していく。
*ngFor ディレクティブ
Angularというフレームワークを用いることでHTMLファイル上で使うことのできるいわゆるfor文のようなものである。
Component単位で画面表示を行うため、TSファイルでデータ管理をする。下記のようにproductsという名前で商品ファイルをimportすることでComponent内、つまりHTMLファイルでも用いることができる。当たり前かもしれないがここはまず1つ重要な関係性である。
import { products } from '../products';
for文を" "
で囲うのは違和感があるが書き方は下記の通りで、importしたproductsからproductという変数名をつけてで一つずつ取り出してdivタグ内で用いるという意味である。
こうしてproduct.nameと記述することでname変数(Angularではプロパティと呼ぶ)を持ってくることができる。
構文補間とドキュメントに書いてあるが{{ }}
を用いることでプロパティの値を表示できる。かっこが2つ必要であることが注意する(1つだとエラーになる)。
<h2>Products</h2>
<!-- #ここがfor文 -->
<div *ngFor="let product of products">
<h3>
{{ product.name }}
</h3>
</div>
プロパティバインディング[ ]
Angularには〇〇バインディングという仕組みが存在する。バインディングとは簡単に言うとデータの受け渡しである。ここはプロパティの受け渡しについてだが、他にもあるので調べるといいかもしれない。
下記のようにtitleという属性値を[ ]
で囲うことで、productのnameプロパティを受け取ることができる。
<h2>Products</h2>
<div *ngFor="let product of products">
<h3>
<!-- #この部分 -->
<a [title]="product.name + ' details'">
{{ product.name }}
</a>
</h3>
</div>
*ngIf ディレクティブ
*ngForに続いて出てくるのが *ngIfディレクティブである。これはif文である。
下記の例だとdescriptionプロパティがあればtrue、pタグを表示するということである。
<h2>Products</h2>
<div *ngFor="let product of products">
<h3>
<a [title]="product.name + ' details'">
{{ product.name }}
</a>
</h3>
<!-- #この部分 -->
<p *ngIf="product.description">
Description: {{ product.description }}
</p>
</div>
イベントバインディング( )
早速次のバインディングが出てきたが、これはイベントの受け渡しである。TSファイルで定義したメソッドをHTML上で用いる、つまり受け渡しをするために必要なのがこのイベントバインディングである。
これもメソッド名を" "
で囲う必要がある。画面にアラートが出れば動作確認完了である。
<h2>Products</h2>
<div *ngFor="let product of products">
<h3>
<a [title]="product.name + ' details'">
{{ product.name }}
</a>
</h3>
<p *ngIf="product.description">
Description: {{ product.description }}
</p>
<!-- #この部分 -->
<button type="button" (click)="share()">
Share
</button>
</div>
子コンポーネントにデータを渡す(親→子は@inputを用いる)
続いて次の章に入るが、私はここから少しずつ理解が難しくなってきたのでなるべくわかりやすく言語化する。
子コンポーネントの生成方法
ここでは先程編集したProductListComponent
の子コンポーネントProductAlertsComponent
を作成する。ここでの親、子という認識は開発者(我々)が勝手に関係を決めているだけということに留意する。
ターミナルで下記コマンドを実行して生成する。
なお、ドキュメントで新しいターミナルを開く理由が書いてないので説明すると、これまで開いていたタブはコンパイル等のホットリロード用だからである。コードを編集すると当ターミナルが自動で動いていることが確認できれば理解できると思う。
書き方としてproduct-alertというcomponentを生成するというように記述する。ここで上記
Angularの基本構成についてで説明した構成単位の理解が重要である。何かを生成する際にはComponentを生成、のようにどの要素を生成するか指定する必要がある。
画面表示をするComponentをここで生成すると、その中にhtml,css,tsファイルが同時に生成される。
ng generate component product-alerts
@Component({
selector: 'app-product-alerts',
templateUrl: './product-alerts.component.html',
styleUrls: ['./product-alerts.component.css']
})
export class ProductAlertsComponent {
}
@Component
には当該Componentのメタデータが自動的に記述される。
selector
はComponent間を相互に繋ぐ場合に用いるいわばComponentの名前である。後に出てくるが、<app-product-alerts></app-product-alerts>
のようにselectorを他Componentのhtmlファイル
で記述することで使用できる。
html,cssはどのファイルとリンクしているかが記述されており、こちらは理解しやすいと思う。頭の隅に入れるとしたらファイル名ではなく、html,cssをここに直接記述することもできる。
@Inputデコレータ
まず、Inputをimportするが、このようにデコレータ等必要なものはライブラリからimportして利用するという形を取る。
!
がproductプロパティの末尾についているがこれはNon-null assertion operator
と呼ばれるものでnullにはなりませんと記述している。
下記によってProductを親Componentから受け取る(input)ことができる。(親子の設定をする必要あり)
import { Component, Input } from '@angular/core';
import { Product } from '../products';
@Component({
selector: 'app-product-alerts',
templateUrl: './product-alerts.component.html',
styleUrls: ['./product-alerts.component.css']
})
export class ProductAlertsComponent {
@Input() product!: Product;
}
こうして下記に上述した*ngIfを用いて700ドル以上であればpタグを表示させる。*ngIfの中身はproduct.price>700
のみでも動作する。
<p *ngIf="product && product.price > 700">
<button type="button">Notify Me</button>
</p>
app.moduleの話が出てくるがターミナル上でng generate component
で生成すると自動的にComponent情報がルートモジュールへ追加される(利用できるようになっている)。
子を親で表示する
上述したようにselector名
を用いてhtmlファイルに記述することで表示することができる(親と子の繋がりの作り方
)。
ここでもプロパティバインディングでproductプロパティ
の受け渡しを行い、product-alerts.component.html
での*ngIfの判定で用いる。
<button type="button" (click)="share()">
Share
</button>
<!-- #この部分 -->
<app-product-alerts [product]="product"></app-product-alerts>
親コンポーネントにデータを渡す(子→親は@outputを用いる)
続いてデータの受け渡しの逆のパターンだが、inputの逆ということでここではoutputを用いる。outputだけでなくEventEmitterが出てくることから、私はここがこのセクションの一番の鬼門となった。
実現させたいことは、上記で表示させたNotify Meボタン(子)
をクリックするとonNotifyメソッド(親)
を実行させることだ。つまり簡単にまとめると、子でボタンがクリックされたというデータを親に渡して、親のメソッドを動かそうということである。
outputとEventEmitter
順に追うために公式ドキュメント通り、子→親の順番でソース(tsとhtmlファイル)を下記に列挙する。
子プログラム
// OutputとEventEmitterをimport
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Product } from '../products';
export class ProductAlertsComponent {
@Input() product: Product | undefined;
// EventEmitterをnotifyという名でインスタンス化
@Output() notify = new EventEmitter();
}
ここでOutputとしてnotify(EventEmitterのインスタンス)を親へ渡すとしている。
<p *ngIf="product && product.price > 700">
<!-- 「clickされたらEventEmitterのemitメソッドを実行する」 という意味 -->
<button type="button" (click)="notify.emit()">Notify Me</button>
</p>
ここではEventEmitterのインスタンスであるnotifyのemitメソッドを実行させる。emit()とはイベントを発行する
メソッドである。イベントとはなんだと言う話になるが、ここでは私はOutputであるnotifyを子から親へ送るアクションだと認識している。つまり、emit()メソッドが実行されると子で定義したnotifyが親でイベントバインディングされていた場合、(notify)が実行されるということである((click)とコードで書いた部分で実際にクリックされることと同じ認識)。
emit()と今回は引数が無く記述しているのでイベントの受け渡しのみである(が、引数を指定すればプロパティも受け渡しができるのではないかと考える)。
親プログラム
export class ProductListComponent {
products = [...products];
share() {
window.alert('The product has been shared!');
}
// onNotifyメソッドを定義。 内容はアラートを出すだけ。
onNotify() {
window.alert('You will be notified when the product goes on sale');
}
}
<button type="button" (click)="share()">
Share
</button>
<!-- EventEmitterのインスタンスをイベントバインディングしてonNotifyを実行する -->
<app-product-alerts [product]="product" (notify)="onNotify()"></app-product-alerts>
ここでは上述したイベントバインディングの中身が(click)ではなくEventEmitterのインスタンスであるnotify
となっている。子コンポーネントでイベントが発生(emit()メソッドが実行)した場合、(notify)が実行、つまりonNotify()メソッドが実行
され、画面にアラートが表示されるという構造となっている。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
おわりに
Angular公式ドキュメントのチュートリアルを進めるにあたっての理解、解説について記述した。1つ目の入門であるが、最後のイベント周りに関して理解に時間がかかった。わからない単語ばかりで全く進まない状態に最初はなると思うが、この記事を参考に理解が深まる、またはよりスムーズに進めてみてほしい。
チュートリアルの各単元ごとに記事を残しておくので続けて参考にリンクに飛んでみてはどうか。では。
次→【Angular】公式チュートリアルの理解2 (ナビゲーションの追加)