LoginSignup
37
55

More than 5 years have passed since last update.

Angular 入門 - アプリケーションを作りながらモジュールとコンポーネントとサービスについて理解する

Last updated at Posted at 2017-08-16

はじめに

  • 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();
+  }
 }
37
55
0

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
37
55