Angular.js ng-repeat across multiple elements - javascript

This question has been partly addressed here: Angular.js ng-repeat across multiple tr's
However that is just a work-around really, it doesn't actually address the core issue, which is: how can one use ng-repeat across multiple elements without a wrapper?
For example, jquery.accordion requires you to repeat an h3 and div element, how could one do this with ng-repeat?

We now have a proper support for this, please see:
AngularJs Commmit
with this change you can now do:
<table>
<tr ng-repeat-start="item in list">
<td>I get repeated</td>
</tr>
<tr ng-repeat-end>
<td>I also get repeated</td>
</tr>
</table>

To answer Andre's question above on more than 2 levels of ng-repeat in a table, you can use multiple ng-repeat-start to accomplish this.
<tr ng-repeat-start="items in list">
<td>{{items.title}}</td>
</tr>
<tr ng-repeat-start="item in items">
<td>{{item.subtitle}}</td>
</tr>
<tr ng-repeat-end ng-repeat="value in item.values">
<td>{{value.col1}}</td>
<td>{{value.col2}}</td>
</tr>
<tr ng-repeat-end></tr>
Here is a plunker example

UPDATE: This answer is outdated. Please see #IgorMinar answer and use standard ng-repeat-start and ng-repeat-end directives.
There are two options:
First option is to create directive that will render several tags and replace source tag (jsfiddle)
<div multi ></div>
angular.module('components').directive('multi', function ($compile) {
return {
restrict: 'A',
scope : {
first : '=',
last : '=',
},
terminal:true,
link: function (scope, element, attrs) {
var tmpl = '', arr = [0,1,2,3]
// this is instead of your repeater
for (var i in arr) {
tmpl +='<div>another div</div>'
}
var newElement = angular.element(tmpl);
$compile(newElement)(scope);
element.replaceWith(newElement);
}
})
Second option is to use updated source code of angular that enables comment style ngRepeat directive (plnkr)
<body ng-controller="MainCtrl">
<div ng-init="arr=[0,1,2]" ></div>
<!-- directive: ng-repeat i in arr -->
<div>{{i}}</div>
<div>{{ 'foo' }}</div>
<!-- /ng-repeat -->
{{ arr }}
<div ng-click="arr.push(arr.length)">add</div>
</body>

Related

Show an image (or icon) when a boolean value is TRUE with AngularJS?

So I have a list which have a column with boolean values in case some item have an attached file, when it does have an attachment it will show a "clip" icon. I want to do the same with an AngularJS table:
This is my code, HTML:
Notice there's a custom filter ("booleanFilter") in the {{link.Attachments}}
<body ng-app="myApp">
<div ng-controller="myController">
<table>
<thead>
<tr>
<th>Attachments</th>
<th>Name</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="link in links">
<td>{{link.Attachments | booleanFilter}}</td>
<td>{{link.Name}}</td>
<td>{{link.Date | date:"MM/dd/yyyy"}}</td>
</tr>
</tbody>
</table>
</div>
</body>
Here's my SCRIPT of my filter:
I want to show an Attachment Image when there is an attachment (when it's true) and show nothing when it's false:
var myApp = angular
.module('myApp', [])
.filter('booleanFilter', function() {
return function(booleanFilter) {
switch(booleanFilter) {
case true:
return "clip.png";
case false:
return "";
}
}
})
The bad thing with this code is that it shows "clip.png" as a string instead of showing the icon picture, also I tried putting a Material Icon's code:
<i class="material-icons">attach_file</i>
But it won't work... so any ideas or is there something I've been doing wrong?
If your flag is Attachments which can have true or false value then you don't even need filter:
<tbody>
<tr ng-repeat="link in links">
<td><i ng-if="link.Attachments" class="material-icons">attach_file</i></td>
<td>{{link.Name}}</td>
<td>{{link.Date | date:"MM/dd/yyyy"}}</td>
</tr>
</tbody>
Try to load local image with:
app.controller('myCtrl', function($scope, $sce) {
$scope.attachImg = $sce.trustAsResourceUrl('path/clip.png');
});
Then use ngSrc directive:
<img ng-src="{{attachImg}}">
You could make is simpler using ng-if
<tr ng-repeat="link in links">
<td>{{link.Attachments | booleanFilter}}</td>
<i ng-if="(link.Attachments)>0" class="material-icons">attach_file</i>
<td>{{link.Name}}</td>
<td>{{link.Date | date:"MM/dd/yyyy"}}</td>
So if "link.Attachment > 0" or in your case can be == true/false then show the icon, applying css class "material-icons" or your custom class

Change view depending on click in Angular2

How can I change the view in my Angular2 template:
<td *ngIf="hour === first">{{obj[0].from}}</td>
<td *ngIf="hour === second">{{obj[1].from}}</td>
<td *ngIf="hour === third">{{obj[2].from}}</td>
This td is a part of bigger table, but you'll get the logic.
Now I have another part of template below:
<div class="info">
<p>{{schedule[0].name}}</p>
<p>{{schedule[1].name}}</p>
<p>{{schedule[2].name}}</p>
</div>
I need to match the click on the first td so that only first p will be displayed in the .info class and others will not be visible, and so with the others: if second td is clicked, the second p should be displayed, other not, etc..
I've tried following this tutorial: http://jilles.me/ng-click-and-ng-if-in-angular2/ but couldn't get it to work. In my actual template there is more stuff besides the p, but I believe the logic should be the same.
How can I do this?
Thanks guys.
Firstly use ngFor for printing the p as follows:
<div class="info">
<p *ngFor="let s of schedule">{{s.name}}</p>
</div>
Secondly use index and add the hidden tag to the p as follows:
<div class="info">
<p *ngFor="let s of schedule; let i = index"
[hidden]="hiddenIndex === i">{{s.name}}</p>
</div>
Thirdly add click method on td which calls a function that sets the hiddenIndex value.
<td *ngIf="hour === first" (click)="setHiddenIndex(0)">{{obj[0].from}}</td>
<td *ngIf="hour === second" (click)="setHiddenIndex(1)">{{obj[1].from}}</td>
<td *ngIf="hour === third" (click)="setHiddenIndex(2)">{{obj[2].from}}</td>
in the class:
hiddenIndex: number;
setHiddenIndex(index: number) {
this.hiddenIndex = index;
}
That's it. My solution uses hidden instead of ngIf. We can't use ngIf and ngFor on one element so if you'd like to use ngIf you need to build your html a bit differently.

AngularJS ng-repeat applied multiple times in $compiled directive

I've written a directive that dynamically creates a popover for an element:
app.directive('popover', function($compile, $timeout){
return {
link: function(scope, element, attrs) {
$timeout(function() {
// grab template
var tpl = $(element).find('.popover-template')
// grab popover parts of template
var template = {
//$compile( $(element).siblings(".pop-content").contents() )(scope)
title: tpl.find('.template-title').contents(),
content: tpl.find('.template-content').contents()
};
// render template with angular
var content = $compile(template.content)(scope);
var title = $compile(template.title)(scope);
$(element).popover({
html: true,
placement: "right",
content: content,
title: title
});
scope.$digest()
});
}
};
});
In application it looks like this:
<span popover>Click me</span>
<div ng-hide="true" class="popover-template">
<div class="template-title">
<strong>{{ x.name }} and {{ y.name }}</strong>
</div>
<div class="template-content">
<div>
<pre>f in [1,2,3]</pre>
<div ng-repeat="f in [1,2,3]">
item {{ f }}, index {{ $index }}
</div>
</div>
</div>
</div>
The popover is created and displayed. The title works correctly as well. However, ng-repeat is applied multiple times in any iteration:
As you can see, the iteration that should only include 3 elements in fact includes 3*3 elements. The directive creates popovers for exactly 3 elements, so I guess that's where my mistake lies. How can I make sure that within each popover, ng-repeat is only called once?
The problem
Since the popover-template element is already in the document when you bootstrapped the angular application (at page load), it has already been compiled once. The ng-repeat element is replaced with 3 new elements:
<!-- original -->
<div ng-repeat="f in [1,2,3]">item {{ f }}, index {{ $index }}</div>
<!-- replaced -->
<div ng-repeat="f in [1,2,3]">item 1, index 0</div>
<div ng-repeat="f in [1,2,3]">item 2, index 1</div>
<div ng-repeat="f in [1,2,3]">item 3, index 2</div>
When you compile it again in the link function, each of the 3 ng-repeats is triggered, making 3 identical copies, 9 total.
The solution
Keep your popover-template in a separate file so it is not compiled on page load. You can then load it with the $templateCache service.
In general, just make sure you don't compile your HTML multiple times.
Instead using the compiled html for the popover template, load the template using $http or templateCache.
The HTML:
<span popover>Click me</span>
<script type="text/ng-template" id="popover.html">
<div class="popover-template">
<div class="template-title">
<strong>{{ x.name }} and {{ y.name }}</strong>
</div>
<div class="template-content">
<div>
<pre>f in [1,2,3] track by $index</pre>
<div ng-repeat="f in [1,2,3]">
item {{ f }}, index {{ $index }}
</div>
</div>
</div>
</div>
</script>
The Javascript:
angular.module('app',[]).directive('popover', function($compile, $timeout, $templateCache){
return {
link: function(scope, element, attrs) {
$timeout(function() {
// grab the template (this is the catch)
// you can pass the template name as a binding if you want to be loaded dynamically
var tpl = angular.element($templateCache.get('popover.html'));
// grab popover parts of template
var template = {
title: tpl.find('.template-title').contents(),
content: tpl.find('.template-content').contents()
};
// render template with angular
var content = $compile(template.content)(scope);
var title = $compile(template.title)(scope);
$(element).popover({
html: true,
placement: "right",
content: content,
title: title
});
scope.$digest()
});
}
};
});
Also, I have made this plunker with an working example: http://embed.plnkr.co/IoIG1Y1DT8RO4tQydXnX/

Expandable content in tables using AngularJS

I've been following the tutorials for AngularJS over on Egghead. Things are going pretty good, until I decided to try to combine some concepts.
My main.js is located here, due to the fact of the size of the file.
And here's my index.html:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<title>Egghead Videos</title>
<link rel="stylesheet" href="foundation/css/foundation.min.css">
</head>
<body>
<div ng-app="myApp">
<div ng-controller="CardsCtrl">
<table>
<tr ng-repeat="set in cards.sets | orderBy:'releaseDate'">
<td>{{set.name}}</td>
<td>{{set.code}}</td>
<td>{{set.releaseDate}}</td>
</tr>
</table>
</div>
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
<script type="text/javascript" src="scripts/main.js"></script>
</body>
</html>
So, as you can see, I have it set up right now to display the set, code, and releaseDate in a table using ng-repeat. What I'm trying to accomplish is every time that you click on a set name, it expands and shows all the cards in that set, showing the name and card number. I've tried wrapping the table in the 'zippy' attribute like the tutorial was doing, but that accomplished nothing. Any ideas or suggestions? Thank you.
So, I'll do this in the table format, but it is weird because it would be within the confines of the first td; you might want to consider a different layout. For visualization sake, I'll just make the set.name into a clickable link, but I believe it can be any element.
Please note that this is not tested; best practice that I've seen on here would be for the question to include either a plnkr.co or jsfiddle.net reference.
View:
<td>
<a href="" ng-click="showThisRow(set)">
{{set.name}}
</a>
<p ng-repeat="card in set.cards" ng-if="showRow">
{{card.number}} : {{card.name}}
</p>
</td>
Controller:
$scope.showThisRow = function (whichSet) {
if (whichSet.showRow == true) {
whichSet.showRow = false;
} else {
whichSet.showRow = true;
}
}
Notes: What this is doing is a nested ng-repeat call, which is shown only if the value of the showRow variable is true. showRow can be instantiated as a method of set (an instance of the first ng-repeat) via the ng-click call, even without being specified within the controller.
I don't know if Angular provides something similar to toggle in jQuery; it would be appreciated if someone who knows that could comment as well.
The trick with selection of individual items of ng-repeat, is the inclusion of an controller in the same element as the ng-repeat. The ng-repeat dedicated controller will contain all the new items generated by the iteration, ng-click calling a function in that controller will do the item selection.
view:
<div ng-controller="TableCtrl">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>Company</th>
</tr>
</thead>
<tbody ng-repeat="i in invoices"
ng-controller="RowCtrl"
ng-click="toggleRow()"
ng-switch on="isSelected()">
<tr>
<td>{{i.name}}</td>
<td>{{i.company}}</td>
</tr>
<tr ng-switch-when="true">
<td>{{i.invoice}}</td>
<td>{{i.units}}</td>
</tr>
</tbody>
</table>
</div>
Controllers:
.value('invoices', [
{ name:'Jack', company:'Blue Widget Co', invoice:'$3,000,000', units: '555'},
{ name:'Jill', company:'Red widget Co', invoice:'$2,000,000', units: '777' }
])
.controller('TableCtrl', function ($scope, invoices) {
$scope.invoices = invoices;
})
.controller('RowCtrl', function ($scope) {
$scope.toggleRow = function () {
$scope.selected = !$scope.selected;
};
$scope.isSelected = function (i) {
return $scope.selected;
};
});
In the RowCtrl, take note of i from ng-repeat="i in invoices":
$scope.isSelected = function (i) {
return $scope.selected;
};
Heres a working expandable table demo
#cheekybastard This can be simplified further by using a directive.
Directive:
app.directive('cdToggleOnClick', function ()
{
return {
restrict: 'A',
scope:
{
cdToggleOnClick: '='
},
link: function(scope, element, attrs) {
element.bind('click', function () {
if (scope.cdToggleOnClick === true)
{
scope.cdToggleOnClick = false;
}
else
{
scope.cdToggleOnClick = true;
}
scope.$apply();
});
}
}
});
HTML:
<div ng-controller="Ctrl">
<table border=1>
<tr ng-repeat-start="i in invoices" cd-toggle-on-click="toggleRow">
<td>{{i.name}}</td>
<td>{{i.company}}</td>
</tr>
<tr ng-repeat-end ng-if="toggleRow">
<td>{{i.invoice}}</td>
<td>{{i.units}}</td>
</tr>
</table>
</div>
Controller:
app.controller('Ctrl', function ($scope) {
$scope.invoices = [
{ name:'Jack', company:'Blue Widget Co', invoice:'$3,000,000', units: '555'},
{ name:'phil', company:'green Widget Co', invoice:'$1,000,000', units: '545'},
{ name:'Jill', company:'Red widget Co', invoice:'$2,000,000', units: '777' }
];
});
Plunker Demo

angular.js ng-repeat li items with html content

I have a model that comes back from the server which contains html instead of text (for instance a b tag or an i tag)
when I use ng-repeat to built a list out of it it shows the html as pure text, is there a built in filter or directive that put's the html inside the li items or not? I've looked in the documentation but since I'm still very new to it I'm having difficulties finding it.
ng-repeat:
<li ng-repeat="opt in opts">
JSFiddle:
http://jsfiddle.net/gFFBa/1/
It goes like ng-bind-html-unsafe="opt.text":
<div ng-app ng-controller="MyCtrl">
<ul>
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="opt.text" >
{{ opt.text }}
</li>
</ul>
<p>{{opt}}</p>
</div>
http://jsfiddle.net/gFFBa/3/
Or you can define a function in scope:
$scope.getContent = function(obj){
return obj.value + " " + obj.text;
}
And use it this way:
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="getContent(opt)" >
{{ opt.value }}
</li>
http://jsfiddle.net/gFFBa/4/
Note that you can not do it with an option tag: Can I use HTML tags in the options for select elements?
Note that ng-bind-html-unsafe is no longer suppported in rc 1.2. Use ng-bind-html instead. See: With ng-bind-html-unsafe removed, how do I inject HTML?
You can use NGBindHTML or NGbindHtmlUnsafe this will not escape the html content of your model.
http://jsfiddle.net/n9rQr/
<div ng-app ng-controller="MyCtrl">
<ul>
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="opt.text">
{{ opt.text }}
</li>
</ul>
<p>{{opt}}</p>
</div>
This works, anyway you should be very careful when using unsanitized html content, you should really trust the source of the content.
use ng-bind-html-unsafe
it will apply html with text inside like below:
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="opt.text" >
{{ opt.text }}
</li>
Here is directive from the official examples angular docs v1.5 that shows how to compile html:
.directive('compileHtml', function ($compile) {
return function (scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compileHtml);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
});
Usage:
<div compile-html="item.htmlString"></div>
It will insert item.htmlString property as html any place, like
<li ng-repeat="item in itemList">
<div compile-html="item.htmlString"></div>
If you want some element to contain a value that is HTML, take a look at ngBindHtmlUnsafe.
If you want to style options in a native select, no it is not possible.
ng-bind-html-unsafe is deprecated from 1.2. The correct answer should be currently:
HTML-side: (the same as the accepted answer stated):
<div ng-app ng-controller="MyCtrl">
<ul>
<li ng-repeat=" opt in opts" ng-bind-html-unsafe="opt.text">
{{ opt.text }}
</li>
</ul>
<p>{{opt}}</p>
</div>
But in the controller-side:
myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {
// ...
$scope.opts.map(function(opt) {
opt = $sce.trustAsHtml(opt);
});
}

Categories