LoginSignup
2
0

More than 3 years have passed since last update.

Angularで動的なデータバインディングを作る

Posted at

Angularでちょっとした表を表示しようとしていて、「やりたいことの変化に柔軟に対応できるよう、セルに表示する値のデータバインディングを動的に変更できたら良いな」と思ったのだが、なかなかやり方が分からなかった。調べたところ、これはAngularのテンプレートだけでは実現できず、lodashの助けが必要だとわかったので、忘れないようやり方をまとめる。

静的なデータバインディング

動的なデータバインディングの前に、普通の静的なデータバインディングだとどうなるかを示す。例えば以下のようなdata配列に対し、配列の要素を行ごとにユーザが指定したプロパティ(例えばcoord.x)だけを表示することを考える。表示したいデータは、下図の赤枠部分とする。
image.png

sample.component.ts
interface SampleData {
  coord: { x: number, y: number };
  value: number;
}

export class SampleComponent {
  ......
  readonly data: SampleData[] = [
    { coord: { x: 1, y: 10 }, value: 100 },
    { coord: { x: 2, y: 20 }, value: 200 },
    { coord: { x: 3, y: 30 }, value: 300 },
    { coord: { x: 4, y: 40 }, value: 400 },
  ];
  ......

表示したいプロパティは変わらないので、Angularのテンプレートで補間(Interpolation: {{と}}で囲む)を使って以下のように書くことができる。

sample.component.html
<p> Current Data: {{ data | json }}</p>
<div *ngFor="let d of data; index as i">
<span>data[{{ i }}].coord.x: </span>
<input [(value)]="d.coord.x">
</div>

表示してみると、行ごとにcoord.xプロパティが表示できていることが確認できる。
image.png

動的なデータバインディング

静的なデータバインディングでも表示したいものが表示できればよいが、coord.xだけでなくcoord.yvalueプロパティを、任意の順番で表示したり動的に切り替えたりしたい場合には対応できない。(ngIfをたくさん作ればある程度はできるが、あまり柔軟ではないし、ドットを含んだ時点で扱いが難しくなる)

動的なデータバインディング(動的と言っていいのかどうか分からないが、とりあえずそう呼ぶ)をするには、あるオブジェクトのプロパティにパス(coord.xのような文字列)を使ってアクセスできる必要がある。これができれば、パスを変更することでバインディング対象のプロパティを変更することができる。こうしたアクセスはlodashのgetset関数で実現できる。(lodash: get, set)

lodashのget/set

オブジェクトのプロパティへ、プロパティまでのパス(文字列)からアクセスできるようになる関数。例えば以下のように、ドット記法を使ったプロパティへのアクセスと同じことを、パスを使ってできるようになる。(その分処理負荷は上がるかと思う)

import { get, set } from 'lodash';

let x;
// プロパティ coord.x の取得
// プロパティ coord.y の設定

// Typescript(Javascript)の書き方
x = data[0].coord.x;
data[0].coord.y = 1;

// lodashでパスを使った書き方
x = get( data[0], 'coord.x' );
set( data[0], 'coord.y', 1 )

lodashを使った動的なデータバインディングの例

lodashを使って、動的なデータバインディングをしてみる。例えばInput要素に、dataで与えられたオブジェクトのbindingで与えられたパスのプロパティを表示し、値が変更されたら反映する単純なコンポーネントCellComponentは以下のようになる。

Input要素へ値を反映するときは、lodashのgetを使って、オブジェクトからプロパティを取得してInput要素のvalueプロパティ渡す。Input要素の値が変化した時(valueChangeイベント)は、lodashのsetを使って元のオブジェクトに反映する。また、Input要素のinputイベントが発生した時は、新しい文字列($event.target.value)をlodashのsetを使って元のオブジェクトに反映する。

import { get, set } from 'lodash';

@Component({
  selector: 'cell',
  template: `<input [value]="get()" (onValueChange)="set($event)" (input)="set($event.target.value)"/>`
})
export class CellComponent {
  @Input() data: any;
  @Input() binding: string;
  get() {
    return get( this.data, this.binding );
  }

  set( value: number ) {
    set( this.data, this.binding, value );
  }
}

動的なバインディングのを動かしてみる

あまり実用的ではないが、配列内のすべてのオブジェクトについて、パスで指定したプロパティをInput要素に表示するコンポーネントDynamicBindingComponentを作ってみる。このコンポーネントでは、ボタンをクリックするたびにchangeBinding()でパスをcoord.x, coord.y, valueの順に切り替えることで、Inputに表示する値を切り替えることができる。
image.png

dynamic-binding.component.ts
@Component({
  selector: 'dynamic-binding',
  templateUrl: './dynamic-binding.component.html',
  styleUrls: ['./dynamic-binding.component.scss']
})
export class DynamicBindingComponent{
  // 表示したいデータ
  readonly data: SampleData[] = [
    { coord: { x: 1, y: 10 }, value: 100 },
    { coord: { x: 2, y: 20 }, value: 200 },
    { coord: { x: 3, y: 30 }, value: 300 },
    { coord: { x: 4, y: 40 }, value: 400 },
  ];

  // バインディング対象のプロパティを示すパス(3種)
  readonly bindings = [ 'coord.x', 'coord.y', 'value' ];

  // バインディングに使用するパス
  index: number = 0;
  binding: string;

  // バインディングに使用するパスを変更する(bindings内のパスを順番に回す)
  changeBinding() {
    this.index++;
    this.binding = this.bindings[ this.index % this.bindings.length ];
  }

  constructor() {
    this.changeBinding();
  }
}

テンプレートは以下のようにして、選択したパスに対するプロパティだけを表示するようにする。

dynamic-binding.component.html
<p> Current Data: {{ data | json }}</p>
<button (click)="changeBinding()">Change Binding</button>
<div *ngFor="let d of data; index as i">
<span>data[{{ i }}].{{ binding }}: </span>
<cell [binding]="binding" [data]="d" ></cell>
</div>

動かした結果

クリックするたびに矢印のように表示が切り替わることが確認できた。

image.png

まとめ

動的にデータバインディングしたいプロパティを変えたいときは、オブジェクトのプロパティへ「パス」を使ってアクセスできるlodashのgetsetを使う。ただし、通常のデータバインディングと違って、オブジェクトを渡しているので、変更検知が意図通り動くかどうかには気を付ける。

2
0
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
2
0