はじめに
- Angular のチュートリアルをやったりドキュメントを読んだりしたので、復習のために自分でアプリケーションを作りながら入門的な内容をまとめていく。
ドキュメント
-
公式のチュートリアル
- これやっておけばなんとなくわかったような気になる
-
公式のアーキテクチャ解説
- コンポーネントとは?モジュールとは?サービスとは?というのがわかる
インストール
- Angular プロジェクトのスケルトンを生成してくれる Angular CLI をインストールする
npm install -g @angular/cli
プロジェクトの作成
ng new myapp
cd myapp
npm install
アプリケーションの起動
- コマンド実行後
http://localhost:4200/
にブラウザでアクセスすることで起動を確認できる
npm start
コンポーネントの作成
- コンポーネントは Angular の管理する UI 要素の単位
- コンポーネントは TypeScript のクラスで内部変数やメソッドを持ち、テンプレートの HTML や CSS を持つことができる
- コンポーネントは入れ子にすることができ、ページとしても UI の要素としても振る舞うことができる
- 今回は ng コマンドでヘッダーパーツのコンポーネントを作成する
コマンドの実行
-
ng generate
コマンドでコンポーネントを作成する - コンポーネントの本体の ts ファイルとテストの spec.ts ファイル、html, css が作成される
- app.module.ts の変更箇所については後述
$ ng generate component header
installing component
create src/app/header/header.component.css
create src/app/header/header.component.html
create src/app/header/header.component.spec.ts
create src/app/header/header.component.ts
update src/app/app.module.ts
コンポーネントの登録
- コンポーネント作成の際に app.module.ts に変更が入ったが、これはアプリケーションのメインモジュールにヘッダーコンポーネントが登録されたということ
- モジュールとは現時点では Angular のフレームワークとしての機能を提供するレイヤーと理解しておけば良い
- 今回はアプリケーション内で利用しているコンポーネントのリストの設定にヘッダーコンポーネントが追加された
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index f657163..55587c2 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -2,10 +2,12 @@ import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
+import { HeaderComponent } from './header/header.component';
@NgModule({
declarations: [
- AppComponent
+ AppComponent,
+ HeaderComponent
],
imports: [
BrowserModule
@NgModule とは
-
@NgModule の箇所は「デコレータ」と呼ばれる TypeScript の機能でクラスやメソッドにメタデータを定義できる
- Java のアノテーションや C# の属性のようなもの
- @NgModule デコレータを付けたクラスは Angular のモジュールとして振る舞う
- @NgModule デコレータのカッコの中には Angular のモジュールの設定を定義する
- declarations にはアプリケーションで使用されるコンポーネントがリストで定義される
作成されたコンポーネントの確認
- 初期化処理として constructor と ngOnInit が定義されている
- constructor は TypeScript の標準のコンストラクタで ngOnInit は Angular のコンポーネントの初期化処理
- constructor には後述の DI のための処理だけを定義して、コンポーネントの初期化処理は ngOnInit に定義する慣習になっている
header.component.ts
import { Component, OnInit } from '@angular/core';
// @Component デコレータがついたクラスがコンポーネントとして振る舞う
@Component({
selector: 'app-header', // HTML上では <app-header> として扱うことができる
templateUrl: './header.component.html', // このコンポーネントに関連するテンプレートの定義
styleUrls: ['./header.component.css'] // このコンポーネントに関連するスタイルシートの定義
})
export class HeaderComponent implements OnInit {
// 初期化処理
constructor() { }
// OnInit インターフェイスを実装すると ngOnInit が初期化時に実行される
ngOnInit() {
}
}
コンポーネントの表示
- app.component.html の行頭にヘッダーのセレクタを記述することでヘッダーコンポーネントを表示することができる
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 230f4ed..5b233f3 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,4 +1,5 @@
<!--The content below is only a placeholder and can be replaced.-->
+<app-header></app-header>
<div style="text-align:center">
<h1>
Welcome to {{title}}!
コンポーネントの値をテンプレートに表示する
変数をテンプレートに表示する
- コンポーネントのインスタンス変数はテンプレート上で
{{VAR}}
という記法で表示できる
diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index 51bfb8c..80a0af3 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -1,3 +1,4 @@
<p>
header works!
+ header title is {{title}}
</p>
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index ac77fc6..f430c29 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -7,6 +7,8 @@ import {Component, OnInit} from '@angular/core';
})
export class HeaderComponent implements OnInit {
+ title = 'タイトル';
+
constructor() {
}
テンプレートでリストを表示する
- テンプレートで
*ngFor
を使うとリストをループで展開することができる - ng-container 要素はコンパイル後のソースには表示されないので今回のように *ngFor などとセットで利用される
diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index 80a0af3..348d0c7 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -2,3 +2,7 @@
header works!
header title is {{title}}
</p>
+
+<ng-container *ngFor="let b of buttons">
+ <button>{{b}}</button>
+</ng-container>
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index f430c29..cbe156a 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -9,6 +9,12 @@ export class HeaderComponent implements OnInit {
title = 'タイトル';
+ buttons: string[] = [
+ 'button1',
+ 'button2',
+ 'button3'
+ ];
+
constructor() {
}
変数で表示非表示を切り替える
-
*ngIf
を使うとコンポーネントの変数の値によってテンプレート上の表示非表示を切り替えることができる
diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index 348d0c7..e09e2ad 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -3,6 +3,9 @@
header title is {{title}}
</p>
-<ng-container *ngFor="let b of buttons">
- <button>{{b}}</button>
+<ng-container *ngIf="showButton">
+ <ng-container *ngFor="let b of buttons">
+ <button>{{b}}</button>
+ </ng-container>
</ng-container>
+
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index cbe156a..5f5301f 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -15,6 +15,8 @@ export class HeaderComponent implements OnInit {
'button3'
];
+ showButton = false;
+
constructor() {
}
ルーティング機能を使って表示するコンポーネントを切り替える
- RouterModule を使うことでテンプレート中の
<router-outlet>
内に任意のコンポーネントを表示することができる - 今回は About ページを追加してページ全体の表示を切り替えられるようにする
- ページ全体を切り替えるような実装にするため既存のトップページの内容は IndexComponent に避難して AppComponent では の表示だけを行う
Index ページ用のコンポーネントの追加
ng g component index
- app.component.html を index.component.html にコピーしておく
About ページ用のコンポーネントの追加
ng g component about
ルーターモジュールの作成
- ルーティング設定を AppModule に直接記載してもいいが、今回はルーティング設定を切り出したいのと自分でモジュールを作ってみたいのでこのアプリケーション用のルーターモジュールをつくってみる
$ ng generate module app-router
installing module
create src/app/app-router/app-router.module.ts
WARNING Module is generated but not provided, it must be provided to be used
ルーターモジュールの設定
- RouterModule が標準のルーターモジュール
- アプリケーション用のルーターモジュールの定数でルーティング設定を用意しておき、RouterModule の import と設定を行う
- RouterModule を export することでこのモジュールを import したモジュール側で RouterModule を改めて import する必要がなくなるので(これだけだとそんなに意味はないが)見通しが良くなり責任が適切に分割できる
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {AppComponent} from '../app.component';
import {AboutComponent} from '../about/about.component';
const routes: Routes = [
{path: '', component: IndexComponent},
{path: 'about', component: AboutComponent},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRouterModule {
}
ルーターモジュールをメインモジュールから読み込む
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 374a2c6..f306a55 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -4,15 +4,19 @@ import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { AboutComponent } from './about/about.component';
+import {AppRouterModule} from './app-router/app-router.module';
+import { IndexComponent } from './index/index.component';
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
AboutComponent,
IndexComponent
],
imports: [
- BrowserModule
+ BrowserModule,
+ AppRouterModule
],
providers: [],
bootstrap: [AppComponent]
ルートパラメータを使用する
ルート定義でパラメータを受け取るように設定
- FooComponent で
bar
パラメータを受け取る設定を追加
AppRouterModule.ts
const routes: Routes = [
{path: '', component: IndexComponent},
{path: 'about', component: AboutComponent},
{path: 'foo/:bar', component: FooComponent},
];
ルートパラメータを受け取る
- FooComponent で
bar
パラメータを受け取る
FooComponent.ts
import {ActivatedRoute} from '@angular/router';
@Component({...})
export class FooComponent implements OnInit {
bar: string;
constructor(private route: ActivatedRoute) {
route.params.subscribe(params => {
this.bar = params['bar'];
});
}
ngOnInit() {
}
}
サービスを作成する
- Angular で言うサービスとは何らかのデータを返したり何らかの処理をするレイヤー
- 公式ドキュメントの例だと logging service, data service, tax calculator, application configuration などとあるので、フレームワークの拡張でもドメインロジックでもなんでもサービスとして扱う様子
- 今回はランダムで運勢を教えてくれるおみくじサービスを作ってみる
$ ng g service omikuji
installing service
create src/app/omikuji.service.spec.ts
create src/app/omikuji.service.ts
WARNING Service is generated but not provided, it must be provided to be used
omikuji.service.ts
import {Injectable} from '@angular/core';
@Injectable()
export class OmikujiService {
box: string[] = [
'大吉',
'中吉',
'吉',
'小吉'
];
constructor() {
}
draw(): string {
return this.box[Math.floor(Math.random() * this.box.length)];
}
}
サービスをコンポーネントから使用する
- Dependency Injection を使ってコンポーネントにサービスを割り当てて使用する
- Dependency Injection とは依存性の注入と訳され、Angular ではサービスや任意のクラスを DI の対象として設定しておくと、初期化と割り当てをフレームワークが自動で行ってくれる
サービスを DI の対象にする
- AppModule の @NgModule デコレータの設定の providers に OmikujiService を登録するとアプリケーション全体で OmikujiService が DI の対象になる
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index f306a55..37fdd84 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -6,6 +6,7 @@ import { HeaderComponent } from './header/header.component';
import { AboutComponent } from './about/about.component';
import {AppRouterModule} from './app-router/app-router.module';
import { IndexComponent } from './index/index.component';
+import {OmikujiService} from './omikuji.service';
@NgModule({
declarations: [
@@ -18,7 +19,7 @@ import { IndexComponent } from './index/index.component';
BrowserModule,
AppRouterModule
],
- providers: [],
+ providers: [OmikujiService],
bootstrap: [AppComponent]
})
export class AppModule { }
サービスをコンポーネントに割り当てて使用する
- コンポーネントのコンストラクタの引数に DI 対象のクラスを指定しておくと初期化時に自動で割り当てられ、コンポーネントから使用できるようになる
diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index e09e2ad..e868060 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -9,3 +9,6 @@
</ng-container>
</ng-container>
+<p>
+ {{omikujiResult}}
+</p>
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index 5f5301f..be4283b 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -1,4 +1,5 @@
import {Component, OnInit} from '@angular/core';
+import {OmikujiService} from '../omikuji.service';
@Component({
selector: 'app-header',
@@ -17,10 +18,13 @@ export class HeaderComponent implements OnInit {
showButton = false;
- constructor() {
+ omikujiResult = '';
+
+ constructor(public omikuji: OmikujiService) {
}
ngOnInit() {
+ this.omikujiResult = this.omikuji.draw();
}
}
クリックイベントを処理する
- テンプレートで
(click)="something()"
とするとその要素のクリックイベントで something メソッドを実行する - 今回はクリックでおみくじを引くように修正する
diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index e868060..51bf2a2 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -9,6 +9,6 @@
</ng-container>
</ng-container>
-<p>
- {{omikujiResult}}
-</p>
+<div>
+ <button (click)="draw()">おみくじを引く</button> {{omikujiResult}}
+</div>
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index be4283b..d5c56e2 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -18,13 +18,15 @@ export class HeaderComponent implements OnInit {
showButton = false;
- omikujiResult = '';
+ omikujiResult;
constructor(public omikuji: OmikujiService) {
}
ngOnInit() {
- this.omikujiResult = this.omikuji.draw();
}
+ draw() {
+ this.omikujiResult = this.omikuji.draw();
+ }
}