ブートストラップを使用してng-repeatデータを3列で分割する方法


122

私のコードでng-repeatを使用しています。ng-repeatに基づいて「n」個のテキストボックスがあります。テキストボックスを3列に揃えたいのですが。

これは私のコードです

<div class="control-group" ng-repeat="oneExt in configAddr.ext">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
           id="macAddress" ng-model="oneExt.newValue" value=""/>
</div>

1
それで、あなたが9を持っていると仮定すると、行または列に1、2、3が必要ですか?
Gabriele Petrioli 2014

回答:


254

最も信頼性が高く、技術的に正しいアプローチは、コントローラーでデータを変換することです。簡単なチャンク関数と使用法を次に示します。

function chunk(arr, size) {
  var newArr = [];
  for (var i=0; i<arr.length; i+=size) {
    newArr.push(arr.slice(i, i+size));
  }
  return newArr;
}

$scope.chunkedData = chunk(myData, 3);

次に、ビューは次のようになります。

<div class="row" ng-repeat="rows in chunkedData">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

内に入力があるng-repeat場合は、データが変更されたとき、または送信時に、配列のチャンクを解除/再結合することをお勧めします。これがでどのように表示されるかを示します。これ$watchにより、データは常に、元のマージされた形式で利用できます。

$scope.$watch('chunkedData', function(val) {
  $scope.data = [].concat.apply([], val);
}, true); // deep watch

多くの人は、フィルターを使用してビューでこれを実現することを好みます。これは可能ですが、表示目的でのみ使用してください。このフィルターされたビュー内に入力を追加すると、解決できる問題が発生しますが、見栄えも信頼性も高くありません

このフィルターの問題は、毎回新しいネストされた配列を返すことです。Angularはフィルターからの戻り値を監視しています。フィルターが初めて実行されるとき、Angularは値を認識しており、それを再度実行して、変更が完了したことを確認します。両方の値が同じ場合、サイクルは終了します。そうでない場合、フィルターは同じになるまで何度も起動します。そうしないと、Angularは無限のダイジェストループが発生していることを認識してシャットダウンします。新しいネストされた配列/オブジェクトは以前Angularによって追跡されていなかったため、戻り値は常に以前とは異なるものとして認識されます。これらの「不安定な」フィルターを修正するには、フィルターをmemoize関数でラップする必要があります。lodash持っているmemoize機能とlodashも含まれる最新バージョンのchunk機能を、私たちは非常に簡単に使用してこのフィルタを作成することができますnpmモジュールおよびbrowserifyまたはでスクリプトをコンパイルしますwebpack

覚えておいてください:表示のみ!入力を使用している場合は、コントローラーでフィルター処理してください。

lodashをインストールします。

npm install lodash-node

フィルターを作成します。

var chunk = require('lodash-node/modern/array/chunk');
var memoize = require('lodash-node/modern/function/memoize');

angular.module('myModule', [])
.filter('chunk', function() {
  return memoize(chunk);
});

そして、これはこのフィルターを使ったサンプルです:

<div ng-repeat="row in ['a','b','c','d','e','f'] | chunk:3">
  <div class="column" ng-repeat="item in row">
    {{($parent.$index*row.length)+$index+1}}. {{item}}
  </div>
</div>

縦に並べる

1  4
2  5
3  6

水平(左から右)ではなく垂直列(上から下にリスト)に関しては、正確な実装は目的のセマンティクスによって異なります。不均等に分割されるリストは、さまざまな方法で配布できます。これが1つの方法です。

<div ng-repeat="row in columns">
  <div class="column" ng-repeat="item in row">
    {{item}}
  </div>
</div>
var data = ['a','b','c','d','e','f','g'];
$scope.columns = columnize(data, 3);
function columnize(input, cols) {
  var arr = [];
  for(i = 0; i < input.length; i++) {
    var colIdx = i % cols;
    arr[colIdx] = arr[colIdx] || [];
    arr[colIdx].push(input[i]);
  }
  return arr;
}

ただし、列を取得するための最も直接的で単純な方法は、CSS列を使用することです。

.columns {
  columns: 3;
}
<div class="columns">
  <div ng-repeat="item in ['a','b','c','d','e','f','g']">
    {{item}}
  </div>
</div>

ご協力いただきありがとうございます。ただし、カルーセルを角度でui-chartと共に使用すると、チャートを描画できません。私のコードは<carousel interval = "myInterval"> <slide ng-repeat = "milestone in mileStone | split:4" active = "slide.active"> <div class = "col-md-3" ng-repeat = " milestone1 in milestone "> <div style =" text-align:center; "> <div ui-chart =" data "chart-options =" chartOptions "> </ div> </ div> </ div> </ slide > </ carousel>そして、スコープ変数をマイルストーンJSONデータとして初期化しました。
kuppu 2014年

1
@ m59現在、この関数は最初の行のa b c2番目の行にデータを表示していますd e fa b 1列c d目を2列e f目、3列目を表示できますか?ありがとう

1
@ Ifch0o1それが正しければ正しいでしょう[].concat.call([], val)。あなたは何をするかを考えなければなりませんapplyvalは配列の配列であり、配列内の各配列を引数としてに渡しますconcat。あなたはのコンテキストに焦点を当てていると思いますが[]、それはここでは重要ではありません。私たちが欲しいのは[].concat(val[1], val[2], val[3], etc)なので、必要ですapply([], val)
m59 '24

1
@bahadir私は答えをより明確にするために更新しました。[]vs [[]]とだけを返すフィルタを作成するとどうなるかを確認してください[{}]。ネストされた配列/オブジェクトは、Angularにごとfilterに異なる結果を表示させ、同じ結果を維持する(安定させる)ために繰り返し検索を続けます。
m59 2015年

1
あなたは本当にコメントをスパムしています:)自分で試してみてください。var x = [1,2,3]; var y = [1,2,3]; console.log(x === y);詳細チェックとは、オブジェクトを文字列化できないために文字列を文字列化して比較すること、または各プロパティを個別に再帰的にチェックすることを意味します。Angular それをフィルターに実装することができたと思いますが、実装しなかったに十分な理由があると私は確信しています。おそらく彼らは主にデータセットの単純な変更のためのフィルターを意図していたのであって、構造への変更による完全なオーバーホールではなかったのでしょう。
m59 2015年

64

このソリューションは非常に簡単です。

JSON:

[{id:"1",name:"testA"},{id:"2",name:"test B"},{id:"3",name:"test C"},{id:"4",name:"test D"},{id:"5",name:"test E"}]

HTML:

<div ng-controller="MyCtrl">
  <table>
    <tr ng-repeat="item in items" ng-switch on="$index % 3">
      <td ng-switch-when="0">
        {{items[$index].id}} {{items[$index].name}}
      </td>
      <td ng-switch-when="0">
        <span ng-show="items[$index+1]">
          {{items[$index+1].id}} {{items[$index+1].name}}
        </span>
      </td>
      <td ng-switch-when="0">
        <span ng-show="items[$index+2]">    
          {{items[$index+2].id}} {{items[$index+2].name}}
        </span>
      </td>
    </tr>
  </table>
</div>

FIDDLEのデモ

ここに画像の説明を入力してください


3
ベストアンサー!そして、ネイティブディレクティブで必要なことをしてください!おめでとう!
Renan Franca

1
このソリューションは機能しますが、ベストプラクティスに関しては、マークアップでレイアウトを定義するためにテーブルを使用するべきではありません:)
Dave Cooper

2
@DaveCooperが言及しているように、テーブルのない別のレイアウトがあります:plnkr.co/edit/81oj8mHaNyfQpCmRBWO4?p=preview。表形式のデータ用のテーブルを検討してください。ただし、それ以外の場合は、モバイルデバイスや可変画面サイズに適したレイアウトです。
Zymotik

1
これは断然最良の解決策です。新しいフレームワークを使用するとすべての癖を見つけるのに時間がかかりますが、ng-switchは天の恵みです
Zoran

テーブルでng-switch-when="0"はなくdivを使用する場合は、3つのアイテムすべてではなく、外側のdivに1回配置できます。
Hans Wouters 2017年

19

データ操作を必要しない、クリーンで適応性のあるソリューション:

HTML:

<div class="control-group" class="label"
    ng-repeat="oneExt in configAddr.ext"
    ng-class="{'new-row': startNewRow($index, columnBreak) }">
    {{$index+1}}. 
    <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress{{$index}}" ng-model="oneExt.newValue" />
</div>

CSS:

.label {
    float: left;
    text-align: left;
}
.new-row {
    clear: left;
}

JavaScript:

$scope.columnBreak = 3; //Max number of colunms
$scope.startNewRow = function (index, count) {
    return ((index) % count) === 0;
};

これは、表示しようとしているデータの配列を操作する必要なく、データを行と列に動的にレンダリングするためのシンプルなソリューションです。さらに、ブラウザウィンドウのサイズを変更しようとすると、グリッドが画面/ divのサイズに動的に適応することがわかります。

(そうしないと、ng-repeatが同じIDで複数の要素を作成しようとするため、IDに{{$ index}}サフィックスも追加しました。)

同様の実用例


1
OPはTwitter Bootstrapの行/列のマークアップを望んでいたと思いますが、これは誰かに役立つかもしれません。ただし、解答にはコードと説明を含める必要があります。リンクのみの回答は削除されます。
m59

@ m59回答を更新しました。提案をありがとう
Cumulo Nimbus

1
大きな+1 はデータ操作を必要とません。これは、ng-repeatを使用してスパン内のデータを2列に分離したいという私のニーズにぴったりでした。これにより、私のBootstrapモーダルはそれほど長くなりませんでした。これが簡単に実装できることに驚きました。<span ng-repeat="z in waiverModalLinks" ng-class="{'new-row':startNewRow($index, columnBreak)}" class="{{z.id}}"><a href="{{z.link}}{{z.title}}">{{z.title}}</a></span>それは「モーダルボディ」の中にあります。
Christine268

15

m59の答えはかなり良いです。私が気に入らないのは、divは、テーブルのデータになる可能性があるものにsをすることです。

m59のフィルター(上記のどこかで答えます)と組み合わせて、テーブルに表示する方法を次に示します。

<table>
    <tr class="" ng-repeat="rows in foos | chunk:2">
        <td ng-repeat="item in rows">{{item}}</td>
    </tr>
</table>

7

以下はより簡単な方法です。

<table>
    <tr ng-repeat="item in lists" ng-hide="$index%2!==0">
        <td>
            <label>{{ lists[$index].name}}</label>
        </td>
        <td ng-hide="!lists[$index+1]">
            <label>{{ lists[$index+1].name}}</label>
        </td>
    </tr>
</table>

Cumulo Nimbusの答えは私には役立ちますが、リストが長すぎる場合にスクロールバーを表示できるdivでラップされたこのグリッドが欲しいです。

これを実現するためstyle="height:200px; overflow:auto"に、テーブルの周りのdiv に追加しました。これにより、単一の列として表示されます。

配列の長さが1の場合に機能するようになりました。


配列に項目が1つしかない場合、最初の行自体は表示されません。この変更を修正するには ng-hide="$index%2!==0"
Anuj Pandey 2016

4

関数があり、それをサービスに保存して、アプリ全体で使用できるようにします。

サービス:

app.service('SplitArrayService', function () {
return {
    SplitArray: function (array, columns) {
        if (array.length <= columns) {
            return [array];
        };

        var rowsNum = Math.ceil(array.length / columns);

        var rowsArray = new Array(rowsNum);

        for (var i = 0; i < rowsNum; i++) {
            var columnsArray = new Array(columns);
            for (j = 0; j < columns; j++) {
                var index = i * columns + j;

                if (index < array.length) {
                    columnsArray[j] = array[index];
                } else {
                    break;
                }
            }
            rowsArray[i] = columnsArray;
        }
        return rowsArray;
    }
}

});

コントローラ:

$scope.rows   = SplitArrayService.SplitArray($scope.images, 3); //im splitting an array of images into 3 columns

マークアップ:

 <div class="col-sm-12" ng-repeat="row in imageRows">
     <div class="col-sm-4" ng-repeat="image in row">
         <img class="img-responsive" ng-src="{{image.src}}">
     </div>
 </div>

これはクールですが、orderByやfilterなどの他のフィルターを使用する場合は役に立ちません。
ロビー・スミス

2

私のアプローチは物事の混合でした。

私の目的は、画面サイズに合わせてグリッドを調整することでした。私はのための3つの列を望んでいたlgため2列、smおよびmd、および1列のxs

最初に、角度$windowサービスを使用して次のスコープ関数を作成しました。

$scope.findColNumberPerRow = function() {
    var nCols;
    var width = $window.innerWidth;

    if (width < 768) {
        // xs
        nCols = 1;
    }
    else if(width >= 768 && width < 1200) {
         // sm and md
         nCols = 2
    } else if (width >= 1200) {
        // lg
        nCols = 3;
    }
    return nCols;
};

次に、@ Cumulo Nimbusによって提案されたクラスを使用しました。

.new-row {
    clear: left;
}

を含むdivに、このページで説明ng-repeatするresizableようにディレクティブを追加しました。これにより、ウィンドウのサイズが変更されるたびに、角度サービスが新しい値で更新されます。$window

最終的に、繰り返されるdivで私が持っている:

ng-repeat="user in users" ng-class="{'new-row': ($index % findColNumberPerRow() === 0) }"

このアプローチの欠陥を教えてください。

お役に立てれば幸いです。


2

Bootstrapが推奨する「clearfix」CSSを使用した簡単なトリック:

<div class="row">
      <div ng-repeat-start="value in values" class="col-md-4">
        {{value}}
      </div>
      <div ng-repeat-end ng-if="$index % 3 == 0" class="clearfix"></div>
</div>

多くの利点:効率的で高速、Boostrapの推奨事項を使用し、考えられる$ digestの問題を回避し、Angularモデルを変更しない。


1

この例では、ネストされたリピーターを作成します。ここで、外部データには、2列にリストしたい内部配列が含まれています。この概念は、3つ以上の列にも当てはまります。

内側の列では、制限に達するまで「行」を繰り返します。制限は、項目の配列の長さを目的の列数(この場合は2)で割ることによって決定されます。除算メソッドはコントローラー上にあり、現在の配列の長さをパラメーターとして渡します。次に、JavaScript slice(0、array.length / columnCount)関数が制限をリピーターに適用しました。

次に、2番目の列リピーターが呼び出され、slice(array.length / columnCount、array.length)を繰り返して、列2に配列の後半を生成します。

<div class="row" ng-repeat="GroupAccess in UsersController.SelectedUser.Access" ng-class="{even: $even, odd: $odd}">
    <div class="col-md-12 col-xs-12" style=" padding-left:15px;">
        <label style="margin-bottom:2px;"><input type="checkbox" ng-model="GroupAccess.isset" />{{GroupAccess.groupname}}</label>
    </div>
    <div class="row" style=" padding-left:15px;">
        <div class="col-md-1 col-xs-0"></div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice(0, state.DivideBy2(GroupAccess.features.length))">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-3 col-xs-2">
            <div style="line-height:11px; margin-left:2px; margin-bottom:2px;" ng-repeat="Feature in GroupAccess.features.slice( state.DivideBy2(GroupAccess.features.length), GroupAccess.features.length )">
                <span class="GrpFeature">{{Feature.featurename}}</span>
            </div>
        </div>
        <div class="col-md-5 col-xs-8">
        </div>
    </div>
</div>


// called to format two columns
state.DivideBy2 = function(nValue) {
    return Math.ceil(nValue /2);
};

これがソリューションを別の方法で見るのに役立つことを願っています。(PSこれは私の最初の投稿です!:-))


1

.rowなしで修正

<div class="col col-33 left" ng-repeat="photo in photos">
   Content here...
</div>

とCSS

.left {
  float: left;
}

1

これは簡単なハック方法です。それはより手動であり、厄介なマークアップで終わります。これはお勧めしませんが、役立つ場合もあります。

ここにフィドルリンクがありますhttp://jsfiddle.net/m0nk3y/9wcbpydq/

HTML:

<div ng-controller="myController">
    <div class="row">
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)">
                {{ person.name }}
                </div>
            </div>
        </div>
        <div class="col-sm-4">
            <div class="well">
                <div ng-repeat="person in people = data | limitTo:Math.ceil(data.length/3):Math.ceil(data.length/3)*2">
                {{ person.name }}
                </div>
            </div>
        </div>
    </div>
</div>

JS:

var app = angular.module('myApp', []);

app.controller('myController', function($scope) {

    $scope.Math = Math;
    $scope.data = [
        {"name":"Person A"},
        ...
    ];
});

この設定では、マークアップで数学を使用する必要があります:/したがって、次の行を追加して数学を挿入する必要があります。 $scope.Math = Math;


1

これらの答えはすべて、過度に設計されているようです。

最も簡単な方法は、col-md-4列のブートストラップで入力divを設定することです。ブートストラップは、ブートストラップの12列の性質により、自動的に3列にフォーマットします。

<div class="col-md-12">
    <div class="control-group" ng-repeat="oneExt in configAddr.ext">
        <div class="col-md-4">
            <input type="text" name="macAdr{{$index}}"
                   id="macAddress" ng-model="oneExt.newValue" value="" />
        </div>
    </div>
</div>

1
<div class="row">
  <div class="col-md-4" ng-repeat="remainder in [0,1,2]">
    <ul>
      <li ng-repeat="item in items" ng-if="$index % 3 == remainder">{{item}}</li>
    </ul>
  </div>
</div>

0

m59の非常に良い答えに基づいています。モデルの入力が変更されるとぼやけるため、一度に変更できるのは1文字だけです。これはそれを必要とする人のためのオブジェクトのリストのための新しいものです:

EDITは、 1つのページに複数のフィルタを処理するために更新しました

app.filter('partition', function() {
  var cache = {}; // holds old arrays for difference repeat scopes
  var filter = function(newArr, size, scope) {
    var i,
      oldLength = 0,
      newLength = 0,
      arr = [],
      id = scope.$id,
      currentArr = cache[id];
    if (!newArr) return;

    if (currentArr) {
      for (i = 0; i < currentArr.length; i++) {
        oldLength += currentArr[i].length;
      }
    }
    if (newArr.length == oldLength) {
      return currentArr; // so we keep the old object and prevent rebuild (it blurs inputs)
    } else {
      for (i = 0; i < newArr.length; i += size) {
        arr.push(newArr.slice(i, i + size));
      }
      cache[id] = arr;
      return arr;
    }
  };
  return filter;
}); 

そして、これは使用法です:

<div ng-repeat="row in items | partition:3:this">
  <span class="span4">
    {{ $index }}
  </span>
</div>

私の知る限り、これはほとんどの時間で機能するはずですが、そのキャッシュロジックは完全に信頼できるようには見えず、スコープで渡すことは不便です。入力が必要な場合は、コントローラ内でフィルタリングすることをお勧めします。とにかくそれは技術的に正しいアプローチです。
m59 2014年

0

私はブートストラップとangularjsの初心者ですが、これにより、4つのアイテムごとの配列を1つのグループとして作成することもでき、結果はほぼ3列になります。このトリックは、ブートストラップのブレークラインの原理を使用しています。

<div class="row">
 <div class="col-sm-4" data-ng-repeat="item in items">
  <div class="some-special-class">
   {{item.XX}}
  </div>
 </div>
</div>

0

Lodashには、個人的に使用するチャンクメソッドが組み込まれています。 ます。https

これに基づくと、コントローラーコードは次のようになります。

$scope.groupedUsers = _.chunk( $scope.users, 3 )

コードを表示:

<div class="row" ng-repeat="rows in groupedUsers">
  <div class="span4" ng-repeat="item in rows">{{item}}</div>
</div>

0

別の方法はwidth:33.33%; float:left、次のようなラッパーdivに設定されます。

<div ng-repeat="right in rights" style="width: 33.33%;float: left;">
    <span style="width:60px;display:inline-block;text-align:right">{{$index}}</span>
    <input type="number" style="width:50px;display:inline-block" ">
</div>

0

同様のケースで、それぞれ3列の表示グループを生成したいと思っていました。しかし、私はブートストラップを使用していましたが、これらのグループを異なる親divに分離しようとしました。また、一般的に役立つものを作りたかった。

私はng-repeat以下のように2でそれに近づきました:

<div ng-repeat="items in quotes" ng-if="!($index % 3)">
  <div ng-repeat="quote in quotes" ng-if="$index <= $parent.$index + 2 && $index >= $parent.$index">
                ... some content ...
  </div>
</div>

これにより、列の数を変更し、いくつかの親divに分離することが非常に簡単になります。


0

誰かがAngular 7(およびそれ以降のバージョン)を必要とする場合に備えて、アプリケーションの1つで使用した例を次に示します。

HTML:

                   <div class="container-fluid">
                    <div class="row" *ngFor="let reports of chunkData; index as i">
                        <div *ngFor="let report of reports" class="col-4 col-sm-4"
                            style="border-style:solid;background-color: antiquewhite">{{report}}</div>
                    </div>
                </div>

.TSファイル

export class ConfirmationPageComponent implements OnInit {
  chunkData = [];
  reportsArray = ["item 1", "item 2", "item 3", "item 4", "item 5", "item 6"];
  
  constructor() {}

  ngOnInit() {
    this.chunkData = this.chunk(this.reportsArray, 3);
  }

  chunk(arr, size) {
    var newArr = [];
    for (var i = 0; i < arr.length; i += size) {
      newArr.push(arr.slice(i, i + size));
    }
    return newArr;
  }
}

これは、データベースから繰り返し処理するアイテムの数に応じて、新しい列/行を動的に作成するための優れたソリューションです。ありがとう!


-1

これは、列で1、2、3を取得する方法である元の質問に答えます。– kuppuが2014年2月8日13:47に質問

angularjsコード:

function GetStaffForFloor(floor) {
    var promiseGet = Directory_Service.getAllStaff(floor);
    promiseGet.then(function (pl) {
        $scope.staffList = chunk(pl.data, 3); //pl.data; //
    },
    function (errorOD) {
        $log.error('Errored while getting staff list.', errorOD);
    });
}
function chunk(array, columns) {
    var numberOfRows = Math.ceil(array.length / columns);

    //puts 1, 2, 3 into column
    var newRow = []; //array is row-based.
    for (var i = 0; i < array.length; i++) {
        var columnData = new Array(columns);
        if (i == numberOfRows) break;
        for (j = 0; j < columns; j++)
        {
            columnData[j] = array[i + numberOfRows * j];
        }
        newRow.push(columnData); 
    }
    return newRow;

    ////this works but 1, 2, 3 is in row
    //var newRow = [];
    //for (var i = 0; i < array.length; i += columns) {
    //    newRow.push(array.slice(i, i + columns)); //push effectively does the pivot. array is row-based.
    //}
    //return newRow;
};

コードを表示(注:ブートストラップ3を使用):

<div class="staffContainer">
    <div class="row" ng-repeat="staff in staffList">
        <div class="col-md-4" ng-repeat="item in staff">{{item.FullName.length > 0 ? item.FullName + ": Rm " + item.RoomNumber : ""}}</div>
    </div>
</div>

-1

このコードは、要素をlgおよびmdモードで3列、smモードで2列、1列がxsモードで要素を整列させるのに役立ちます

<div class="row">
<div ng-repeat="oneExt in configAddr.ext">
    <div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
        {$index+1}}. 
        <input type="text" name="macAdr{{$index+1}}" 
       id="macAddress" ng-model="oneExt.newValue" value=""/>
    </div>
</div>

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.