Array を指定した個数で分割する ChunkPipe を作った

作ったってほどの話ではないです。

Bootstrap を使っていると、Array に入っている要素からグリッドを作成していことがあります。あるんです。

<!-- こんなデータがあったとして

let items = [
  { name: "はてなブログ", url: "http://hatenablog.com/" },
  { name: "はてなブックマーク", url: "http://b.hatena.ne.jp/" },
  { name: "人力検索はてな", url: "http://q.hatena.ne.jp/" },
  { name: "はてなアンテナ", url: "http://a.hatena.ne.jp/" },
  { name: "はてなキーワード", url: "http://k.hatena.ne.jp/" }
];

-->
<div class="container">
  <div class="row">
    <!-- container > row の中に col-md-4 のグリッドが 5 つ入ってしまう -->
    <div class="col-md-4" *ngFor="let item of items"> 
      <p><a [href]="item.url">{{ item.name }}</a></p>
    </div>
  </div>
</div>

Bootstrap 的には以下のように定義するべきだと思います。 (テンプレート展開後)

<div class="container">
  <div class="row">
    <div class="col-md-4"> 
      <p><a href="http://hatenablog.com/">はてなブログ</a></p>
    </div>
    <div class="col-md-4"> 
      <p><a href="http://b.hatena.ne.jp/">はてなブックマーク</a></p>
    </div>
    <div class="col-md-4"> 
      <p><a href="http://q.hatena.ne.jp/">人力検索はてな</a></p>
    </div>
  </div>
  <div class="row">
    <div class="col-md-4"> 
      <p><a href="http://a.hatena.ne.jp/">はてなアンテナ</a></p>
    </div>
    <div class="col-md-4"> 
      <p><a href="http://k.hatena.ne.jp/">はてなキーワード</a></p>
    </div>
  </div>
</div>

div.rowdiv.col-md-4 の 2 つのループが登場することになります。 まず元の Array を 3 個に分割して、その要素をループして・・・となりますが、元の Array を分割する処理をどこに書くか ? という問題が起きました。

今回は、Array を指定した個数で分割する ChunkPipe を定義して使ってみようと思います。

定義

ChunkPipe の定義は以下のとおりです。

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'chunk'
})
export class ChunkPipe implements PipeTransform {

  transform(value: any, args?: any): any {
    let array = value;
    let count = args;

    let len = Math.round(array.length / count);
    let chunked = [];
    for (let i = 0; i < len; i++) {
      chunked.push(array.slice(i * count, i * count + count))
    }
    return chunked;
  }

}

PipeTransform を実装した ChunkPipe クラスを定義します。 Pipe の定義方法は以下のドキュメントを参照してください。

Pipes - ts - GUIDE

このような処理は、PHP には array_chunk 関数で用意されています。 もちろん、NPM にもあります。

www.npmjs.com

使い方

row のループをする際に、ChunkPipe でまず Array を分割して、その後分割した Array で div.col-md-4 をループします。

<div class="container">
  <!-- row を ngFor でループする際に、chunk:3 を指定し、要素数が 3 個の Array を生成する。 -->
  <div class="row" *ngFor="let chunked of items | chunk:3">
    <!-- col-md-4 をループする -->
    <div class="col-md-4" *ngFor="let item of chunked"> 
      <p><a [href]="item.url">{{ item.name }}</a></p>
    </div>
  </div>
</div>

すると、あるべき姿の HTML を生成することができます。

注意点

今回定義した Pipe は、pure な Pipe を定義しています。 Array 自体や Array の中身を変更しても画面に表示されない場合、impure なPipe に変更すると上手くいくかもしれません。

pureimpure については、@mitsuruog さんのブログ記事で解説されています。

blog.mitsuruog.info

ただし、性能はちょっと悪くなる可能性もあります。

まとめ

Angular で Array を表示する際のちょっとした小ネタでした。 Component 側で分割した Array を作っている方などはぜひ使ってみてください。