このエントリーをはてなブックマークに追加

もくじ

はじめに

カルテットコミュニケーションズ開発部ブログで Angular超入門 の連載をスタートしてから、かなりの間が空いてしまいました。🙇前回の投稿からすでに半年が経過してしまいましたが、ぼちぼち再開しようと思います。また、金本からのバトンタッチを受け、今回から松岡が執筆を担当します。

さて、前回の投稿 Todoアプリ開発開始!新しくコンポーネントを作ってみよう! では todo を入力するためのシンプルなコンポーネントを作成しました。

ブラウザにはこのような入力画面が表示されます。

ちょっと味気ないですね…。今回はこのコンポーネントをベースに todo をリスト管理できるようにしてみたいと思います!

まだチュートリアルを実践していない方は Hello Worldしてみよう! から試してみてくださいませ。10分足らずで終わるはずです。

今回のゴール

todo リストを表示し、追加ボタンでリストの要素を増やせるようにします。

前回作った TodoFormComponent に加え、今回は TodoListComponent を新たに追加します。この2つのコンポーネントの親である AppComponent を通して todo データをやり取りしてみましょう。

ちなみに本文中の「親」「子」とは、とあるコンポーネント(親)に別のコンポーネントを設置している(子)、という関係を指しています。

DOM のイベントを拾ってみよう!

ボタンを設置する

前回までのチュートリアルのソースコードをベースに TodoFormComponent に追加ボタンを設置してみましょう。

src/app/components/todo-form/todo-form.component.html

<input type="text" [(ngModel)]="todo.name">
<input type="checkbox" [(ngModel)]="todo.isDone">

+ <button (click)="clicked()">追加</button>

src/app/components/todo-form/todo-form.component.ts

@Component({
  selector: 'app-todo-form',
  ...
})
export class TodoFormComponent implements OnInit {
  ...
+  clicked() {
+    console.log(this.todo);
+  }

イベントバインディング

(click) のような書き方を イベントバインディング と呼びます。 (change) (focus) (blur) など () には DOM の標準イベント の名前が入ります。

(click)="clicked()" のように書くと イベント発火時にコンポーネントのメソッドを呼び出す 事ができます。

[ユーザー入力イベントにバインドする angular.jp](https://angular.jp/guide/user-input#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E5%85%A5%E5%8A%9B%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AB%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89%E3%81%99%E3%82%8B)

これで、追加ボタンを押した時に任意の処理が実行できるように準備が整いました。

ng serve --open してブラウザで追加ボタンをクリックしてみてください。入力した todo の内容がデバッグコンソールに出力されるはずです。

親子関係のコンポーネントでデータを連携させてみよう!

子コンポーネントでイベント発火する

TodoFormComponent は親である AppComponent に「追加ボタンが押された」事を知らせる必要があります。

子コンポーネントから親コンポーネントに 何らかのタイミング を伝えるには @OutputEventEmitter を使います。

src/app/components/todo-form/todo-form.component.ts

- import { Component, OnInit } from '@angular/core';
+ import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Todo } from 'src/app/models/todo';

@Component({
  selector: 'app-todo-form',
  ...
})
export class TodoFormComponent implements OnInit {

+  @Output() addTodo = new EventEmitter<Todo>();

  public todo: Todo = {
    name: '',
    isDone: false,
  };

  constructor() { }

  ngOnInit(): void {
  }

  clicked() {
-    console.log(this.todo);
+    this.addTodo.emit(this.todo);
  }

}

@Output() / EventEmitter

カスタムイベントを発火させる仕組みです。 @Output() addTodo はこのコンポーネントが addTodo というカスタムイベントを持っている事を宣言 しています。emit() を呼び出すと カスタムイベントが発火 します。

emit() には、カスタムイベントのパラメータを渡す事ができます。

  • 数値 this.addTodo.emit(1)
  • 文字列 this.addTodo.emit("abc")
  • オブジェクト this.addTodo.emit({ id: 1 })

上記のソースコードでは this.addTodo.emit(this.todo) としているため、コンポーネントの持つ todo をパラメータとして渡している事になります。

[親が子のイベントをリッスンする angular.jp](https://angular.jp/guide/component-interaction#%E8%A6%AA%E3%81%8C%E5%AD%90%E3%81%AE%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%92%E3%83%AA%E3%83%83%E3%82%B9%E3%83%B3%E3%81%99%E3%82%8B)

これで、TodoFormComponent が入力内容の todo を親コンポーネントに伝えられるようになりました。

親コンポーネントがイベントを拾う

次に親コンポーネントである AppComponent で「追加ボタンが押された」イベントを拾ってみましょう。

DOMの標準イベントをキャッチするのに (click)="method()" と書くように、カスタムイベントも (イベント名)="method()" という書き方をします。

src/app/app.component.html

- <app-todo-form></app-todo-form>
+ <app-todo-form (addTodo)="added($event)"></app-todo-form>

パラメータを受け取る時はイベントバインディングの引数に $event を指定します。

src/app/app.component.ts

import { Component } from '@angular/core';
+ import { Todo } from './models/todo';

@Component({
  selector: 'app-root',
  ...
})
export class AppComponent {
  title = 'angular-todo';

+  public appTodos: Todo[] = [];

+  added(todo: Todo) {
+    this.appTodos.push({
+      name: todo.name,
+      isDone: todo.isDone,
+    });
+  }
}

added() の引数名は、$event に縛られません。任意に変更できます。

これで、AppComponent が TodoFormComponent のイベントを拾って、受け取った todo をリストに追加するようになりました。

リスト表示用コンポーネントを作成

次に AppComponent が持っている todo のリストを画面に表示するために、専用のコンポーネントを作成します。

$ ng g c components/todo-list

ng generate component コマンドは頭文字を取ってこのように短くタイプする事もできます。

コマンドによって <todo-list> が追加されているはずです。

> $ tree src/app/components/
src/app/components/
├── todo-form
│   ├── ...
└── todo-list
    ├── todo-list.component.html
    ├── todo-list.component.scss
    ├── todo-list.component.spec.ts
    └── todo-list.component.ts

子コンポーネントが値を受け取る準備をする

TodoListComponent は親コンポーネントである AppComponent から todo のリストを受け取る必要があります。

子コンポーネントが 親コンポーネントから値を受け取る には @Input を使います。

src/app/components/todo-list/todo-list.component.ts

- import { Component, OnInit } from '@angular/core';
+ import { Component, Input, OnInit } from '@angular/core';
import { Todo } from 'src/app/models/todo';

@Component({
  selector: 'app-todo-list',
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.scss']
})
export class TodoListComponent implements OnInit {

+  @Input() todos: Todo[] = [];

  constructor() { }

  ngOnInit(): void {
  }

}

@Input()

データバインディングでデータを受け取る仕組みです。 @Input() todos のように宣言すると親コンポーネントから [todos]="値" のように値を受け渡しする事ができます。

これで TodoListComponent が todo のリストを受け取る準備ができました。何が渡されたのか分かるようにオブジェクトの中身をデバッグ出力しておきましょう。

src/app/components/todo-list/todo-list.component.html

- <p>todo-list works!</p>
+ <pre>{{todos|json}}</pre>

親コンポーネントから子コンポーネントに値を渡す

さて、ようやく連携の準備が整いました。AppComponent の HTML に リスト表示コンポーネントを設置 してみましょう。

src/app/app.component.html

<app-todo-form (addTodo)="added($event)"></app-todo-form>
+ <app-todo-list [todos]="appTodos"></app-todo-list>

AppComponent の動作:

  • (addTodo) イベントバインディングを通して TodoFormComponent から todo を受け取ります
  • 受け取った todo を変数 appTodos に追加します
  • [todos] データバインディングを通して TodoListComponent に appTodos を渡します

動作確認

ブラウザで確認してみてください。 TodoFormComponent で入力した内容が、親である AppComponent を通して TodoListComponent に伝わるはずです。

まとめ

DOM の標準イベントを拾う。

<button (name)="method()">

子コンポーネントのイベントを拾う。

class ChildComponent {
  @Output() myEvent = new EventEmitter<ParamType>();

  method() {
    const param: ParamType = {...};
    this.myEvent.emit(param);
  }
}
// 親
<child-component (myEvent)="method($event)">

子コンポーネントに値を渡す。

class ChildComponent {
  @Input() name: Type;
}
// 親
<child-component [name]="value">

次回予告

次回は、繰り返しを制御する *ngFor や分岐を制御する *ngIf を使って、ビューをカスタマイズしてみたいと思います。

お楽しみに! :raised_hands:


このエントリーをはてなブックマークに追加

今回は Application Load Balancer(以降、ALB )の設定時につまづいたことおよびそれをきっかけに制限緩和専用のコンソールを知りましたのでそのログをメモがてら記します。

経緯

CloudFormation で

ある ALB に「複数のIPアドレスからのアクセスを任意のターゲットグループに転送する」というリスナールールを設置したく以下のように CloudFormation のテンプレートを記述しました。

LoadBalancerListenerRule:
  Type: AWS::ElasticLoadBalancingV2::ListenerRule
  Properties:
    Actions:
      -
        Type: forward
        TargetGroupArn: !Ref TargetGroup
    Conditions:
      -
        Field: source-ip
        SourceIpConfig:
          Values:
            - "nn0.mm0.xx0.yy0/32"
            - "nn1.mm1.xx1.yy1/32"
            - "nn2.mm2.xx2.yy2/32"
            - "nn3.mm3.xx3.yy3/32"
            - "nn4.mm4.xx4.yy4/32"
            - "nn5.mm5.xx5.yy5/32"
    ListenerArn: !Ref LoadBalancerListener
    Priority: 1

テンプレートのバリデーションチェックも通り、当該スタックの変更セットも無事作成されたので、そのまま変更セットを適用しようとしたところ、以下のようなエラー・メッセージが。

A rule can only have ‘5’ condition values (Service: AmazonElasticLoadBalancingV2;…

そんな仕様あったのでしょうか?ドキュメントを読んでみますがそれらしい表現はありません。

Conditions The conditions.

The rule can optionally include up to one of each of the following conditions: http-request-method, host-header, path-pattern, and source-ip. A rule can also optionally include one or more of each of the following conditions: http-header and query-string.

Required: Yes

Type: List of RuleCondition

Update requires: No interruption

AWS::ElasticLoadBalancingV2::ListenerRule - AWS CloudFormation

とりあえず今回はエラー・メッセージに従って対応することとしますが、これ以降の開発においても「エラー・メッセージが出てきて初めて仕様を知った」では手間がかかります。どこかに仕様はまとまっていないでしょうか。

Webコンソールではどうだったか?

実は CloudFormation でテンプレートを記述する前段としてWebコンソールからのルール設定を試していました。しかし、このときはIPアドレスを5つ登録していたわけではありませんでした。
そこで再度ルール設定にIPアドレスを5つ足す試みをWebコンソールからやってみることにします。
そうしたところ以下のような表示となりました。

web-console

条件値制限(5/ルール)に一致しています。

と記述してあります。たしかに5つまでしか設定できないようですね。そして 制限 としてなにやらリンクが貼られています。

Quotas for Your Application Load Balancers - Elastic Load Balancing

ありました!
どうやら ALB の制限値がまとまって記載されているようです。CloudFormation のドキュメントだけを見ていてもわからないことがありますね。また、これによると制限値がリージョンごとに設定されているようです。この立て付けだと CloudFormation のテンプレートのバリデーションチェックではあぶり出すのが難しいかもしれません。
AWS CLI で現在のアカウントに設定されている ALB に関する制限値の一覧を見ることもできるようです。

$ aws elbv2 describe-account-limits
{
    "Limits": [
        {
            "Max": "3000",
            "Name": "target-groups"
        },
        {
            "Max": "1000",
            "Name": "targets-per-application-load-balancer"
        },
        {
            "Max": "50",
            "Name": "listeners-per-application-load-balancer"
        },
        {
            "Max": "100",
            "Name": "rules-per-application-load-balancer"
        },
        {
            "Max": "50",
            "Name": "network-load-balancers"
        },
        {
            "Max": "3000",
            "Name": "targets-per-network-load-balancer"
        },
        {
            "Max": "500",
            "Name": "targets-per-availability-zone-per-network-load-balancer"
        },
        {
            "Max": "50",
            "Name": "listeners-per-network-load-balancer"
        },
        {
            "Max": "5",
            "Name": "condition-values-per-alb-rule"
        },
        {
            "Max": "5",
            "Name": "condition-wildcards-per-alb-rule"
        },
        {
            "Max": "100",
            "Name": "target-groups-per-application-load-balancer"
        },
        {
            "Max": "5",
            "Name": "target-groups-per-action-on-application-load-balancer"
        },
        {
            "Max": "1",
            "Name": "target-groups-per-action-on-network-load-balancer"
        },
        {
            "Max": "50",
            "Name": "application-load-balancers"
        }
    ]
}

おそらく今回引っかかった制限は condition-values-per-alb-rule と思われます。
なお、このうちいくつかの値についてはこれらを緩和申請もできるとのこと。
AWS Service Quotas という AWS の各種サービスの制限値を包括的に管理することができるコンソールが用意されているとのことで、そちらに所望の値があればここから緩和申請が可能のようです。去年の7月にリリースされていた ようです。知りませんでした!これは便利そうですね!

なお現時点では condition-values-per-alb-rule については AWS Service Quotas からも、サポートセンター からも緩和申請は不可能のようでした。

まとめ

CloudFormation におけるテンプレートのバリデーションチェック時および変更セット作成時には制限値を超えた場合に検出するのは不可能のようです。よって変更セットの適用時に数値に絡むエラーになった場合は制限値であることを疑ってみて、そして可能だったら制限緩和を申請するということは選択肢として頭に入れておくと良さそうですね。

余談

LoadBalancerListenerRule:
  Type: AWS::ElasticLoadBalancingV2::ListenerRule
  Properties:
    Actions:
      -
        TargetGroupArn: !Ref TargetGroup
        Type: forward
    Conditions:
      -
        Field: source-ip
        SourceIpConfig:
          Values:
            - "nn0.mm0.xx0.yy0/32"
            - "nn1.mm1.xx1.yy1/32"
            - "nn2.mm2.xx2.yy2/32"
            - "nn3.mm3.xx3.yy3/32"
            - "nn4.mm4.xx4.yy4/32"
-           - "nn5.mm5.xx5.yy5/32"
+     -
+       Field: source-ip
+       SourceIpConfig:
+         Values:
+           - "nn5.mm5.xx5.yy5/32"
    ListenerArn: !Ref LoadBalancerListener
    Priority: 1

余談ですが、先述のエラーを受けて上記のように修正したのですがこれもまた下記のようなメッセージとともにエラーになりました。

A rule can only have one ‘source-ip’ condition (Service: AmazonElasticLoadBalancingV2…

上記のドキュメントも読んでみたのですがこれについての記述は見つけられませんでした。
とりあえず試してみたところ以下の記述で無事に所望のリソースが作成できました。

LoadBalancerListenerRule1:
  Type: AWS::ElasticLoadBalancingV2::ListenerRule
  Properties:
    Actions:
      -
        TargetGroupArn: !Ref TargetGroup
        Type: forward
    Conditions:
      -
        Field: source-ip
        SourceIpConfig:
          Values:
            - "nn0.mm0.xx0.yy0/32"
            - "nn1.mm1.xx1.yy1/32"
            - "nn2.mm2.xx2.yy2/32"
            - "nn3.mm3.xx3.yy3/32"
            - "nn4.mm4.xx4.yy4/32"
-           - "nn5.mm5.xx5.yy5/32"
    ListenerArn: !Ref LoadBalancerListener
    Priority: 1
+LoadBalancerListenerRule2:
+  Type: AWS::ElasticLoadBalancingV2::ListenerRule
+  Properties:
+    Actions:
+      -
+        TargetGroupArn: !Ref TargetGroup
+        Type: forward
+    Conditions:
+      -
+        Field: source-ip
+        SourceIpConfig:
+          Values:
+            - "nn5.mm5.xx5.yy5/32"
+    ListenerArn: !Ref LoadBalancerListener
+    Priority: 2

ドキュメントをつぶさに見ることはもちろん大切ですが試行錯誤も欠かせませんね。


このエントリーをはてなブックマークに追加

4/4(土)、Symfony Meetup Kansaiの第三回に参加しました。今回は新型コロナウィルス対策としてオンラインで開催されました。
セッション枠で、最近カルテット開発部PHPチームで取り組んでいることについて発表してきました。

発表と言ってもオンライン開催なので自宅からzoomでスライドを画面共有して、話したいことを話すだけです。
基本的には、遠くまで出かけなくていいし、家族からもぎ取る自由時間も短くて済んでオンライン勉強会最高!と思うのですが、発表者としては普段のオフラインの勉強会と違って困った点もありました。

発表者ノートが利用できない

zoom画面共有で発表する以上、発表者ノートが参照できませんでした。
やり方を工夫すればもしかしたらできるのかもしれませんが、いかんせん自宅のマシン環境が非力なMacBookAir1台のみ(追加ディスプレイはなし)ということもあり、スライドの公開用画面を文字多めにして読み上げる形を取ることでなんとか発表を成立させました。

聴衆の反応がわからない(わかりにくい)

オフラインの勉強会だと見える皆さんの反応が感じにくいです。
普段、顔の表情や首を傾げる仕草などで「伝わってないなー」と思ったらアドリブで説明を追加するとか、ウンウン頷いている人が多ければ冗長な説明を飛ばして次に行ったりするんですが、そういう調整は無理でした。ギャグがすべったかウケたかもわかりにくく、笑いを取りたい私としてはつらく感じました…。


とはいえ、最後まで話し終わったあとで色々質問もいただき、興味を持ってもらえたようで嬉しかったです。
今回特に主張したかったのは、技術的に新しい言語や新しいパラダイムや新しいフレームワークにこだわらなくても、まだまだPHPにもSymfonyにも未知の領域・未踏の領域があって、「新しいこと」はできるということです。
「新しいから」「流行ってるから」という理由で、作りたいものに対して最適でない技術を選択してしまう例をときどき聞きます。
私は作りたいものがWebアプリケーションであればPHPはちゃんと現実的でときには最適な選択肢だと思っていますし、PHPやSymfonyを使いながらでも技術的な挑戦はできると考えています。