I am currently using this piece of code to render a list:
<ul ng-cloak>
<div ng-repeat="n in list">
<li>{{ n[0] }}</li>
<li class="divider"></i>
</div>
<li>Additional item</li>
</ul>
However, the <div> element is causing some very minor rendering defects on some browsers.
I would like to know is there a way to do the ng-repeat without the div container, or some alternative method to achieve the same effect.
As Andy Joslin said they were working on comment based ng-repeats but apparently there were too many browser issues. Fortunately AngularJS 1.2 adds built-in support for repeating without adding child elements with the new directives ng-repeat-start and ng-repeat-end.
Here's a little example for adding Bootstrap pagination:
<ul class="pagination">
<li>
«
</li>
<li ng-repeat-start="page in [1,2,3,4,5,6]">{{page}}</li>
<li ng-repeat-end class="divider"></li>
<li>
»
</li>
</ul>
A full working example can be found here.
John Lindquist also has a video tutorial of this over at his excellent egghead.io page.
KnockoutJS containerless binding syntax
Please bear with me a second: KnockoutJS offers an ultra-convenient option of using a containerless binding syntax for its foreach binding as discussed in Note 4 of the foreach binding documentation.
http://knockoutjs.com/documentation/foreach-binding.html
As the Knockout documentation example illustrates, you can write your binding in KnockoutJS like this:
<ul>
<li class="header">Header item</li>
<!-- ko foreach: myItems -->
<li>Item <span data-bind="text: $data"></span></li>
<!-- /ko -->
</ul>
I think it is rather unfortunate AngularJS does not offer this type of syntax.
Angular's ng-repeat-start and ng-repeat-end
In the AngularJS way to solve ng-repeat problems, the samples I come across are of the type jmagnusson posted in his (helpful) answer.
<li ng-repeat-start="page in [1,2,3,4,5]">{{page}}</li>
<li ng-repeat-end></li>
My original thought upon seeing this syntax is: really? Why is Angular forcing all this extra markup that I want nothing to do with and that is so much easier in Knockout? But then hitautodestruct's comment in jmagnusson's answer started making me wonder: what is being generated with ng-repeat-start and ng-repeat-end on separate tags?
A cleaner way to use ng-repeat-start and ng-repeat-end
Upon investigation of hitautodestruct's assertion, adding ng-repeat-end on to a separate tag is exactly what I would not want to do in most cases, because it generates utterly usesless elements: in this case, <li> items with nothing in them. Bootstrap 3's paginated list styles the list items so that it looks like you did not generate any superfluous items, but when you inspect the generated html, they are there.
Fortunately, you do not need to do much to have a cleaner solution and a shorter amount of html: just put the ng-repeat-end declaration on the same html tag that has the ng-repeat-start.
<ul class="pagination">
<li>
«
</li>
<li ng-repeat-start="page in [1,2,3,4,5]" ng-repeat-end></li>
<li>
»
</li>
</ul>
This gives 3 advantages:
less html tags to write
useless, empty tags are not generated by Angular
when the array to repeat is empty, the tag with ng-repeat won't get generated,
giving you the same advantage Knockout's containerless binding gives you in this regard
But there is still a cleaner way
After further reviewing the comments in github on this issue for Angular, https://github.com/angular/angular.js/issues/1891,
you do not need to use ng-repeat-start and ng-repeat-end to achieve the same advantages.
Instead, forking again jmagnusson's example, we can just go:
<ul class="pagination">
<li>
«
</li>
<li ng-repeat="page in [1,2,3,4,5,6]">{{page}}</li>
<li>
»
</li>
</ul>
So when to use ng-repeat-start and ng-repeat-end? As per the angular documentation, to
...repeat a series of elements instead of just one parent element...
Enough talk, show some examples!
Fair enough; this jsbin walks through five examples of what happens when you do and when you don't use ng-repeat-end on the same tag.
http://jsbin.com/eXaPibI/1/
ngRepeat may not be enough, however you can combine that with a custom directive. You could delegate the the task of adding divider items to code if you don't mind a little bit of jQuery.
<li ng-repeat="item in coll" so-add-divide="your exp here"></li>
Such a simple directive doesn't really need an attribute value but might give you lots of possiblities like conditionally adding a divider according to index, length, etc or something completely different.
I recently had the same problem in that I had to repeat an arbitrary collection of spans and images - having an element around them was not an option - there's a simple solution however, create a "null" directive:
app.directive("diNull", function() {
return {
restrict: "E",
replace: true,
template: ""
};
});
You can then use a repeat on that Element, where element.url points to the template for that element:
<di-null ng-repeat="element in elements" ng-include="element.url" ></di-null>
This will repeat any number of different templates with no container around them
Note: hmm I could've sworn blind this removed the di-null element when rendering, but checking it again it doesn't...still solved my layout issues though...curioser and curioser...
for a solution that really works
html
<remove ng-repeat-start="itemGroup in Groups" ></remove>
html stuff in here including inner repeating loops if you want
<remove ng-repeat-end></remove>
add an angular.js directive
//remove directive
(function(){
var remove = function(){
return {
restrict: "E",
replace: true,
link: function(scope, element, attrs, controller){
element.replaceWith('<!--removed element-->');
}
};
};
var module = angular.module("app" );
module.directive('remove', [remove]);
}());
for a brief explanation,
ng-repeat binds itself to the <remove> element and loops as it should, and because we have used ng-repeat-start / ng-repeat-end it loops a block of html not just an element.
then the custom remove directive places the <remove> start and finish elements with <!--removed element-->
There is a comment directive restriction, but ngRepeat doesn't support it (since it needs an element to repeat).
I think I saw the angular team say they would work on comment ng-repeats, but I'm not sure. You should open an issue for it on the repo. http://github.com/angular/angular.js
There is no Angular magic way to do this, for your case you can do this, to get valid HTML, if you are using Bootstrap. Then you will get same effect as adding the li.divider
Create a class:
span.divider {
display: block;
}
Now change your code to this:
<ul ng-cloak>
<li div ng-repeat="n in list">
{{ n[0] }}
<span class="divider"></span>
</li>
<li>Additional item</li>
</ul>
Related
Case in point, I am developing a multi-select control in Angular2, similar in functionality to Select2 or a multitude of other controls.
I started by defining what I want the user interface to look like in terms of defining what's included in the dropdown, and came up with two options.
One is to use #Input()s for the options:
<my-multi-select [options]="options"></my-multi-select>
...and then within the template for my-multi-select:
<div class=option" *ngFor="let option of options">{{option.display}}</div>
Ahother is to use transclusion, which is how material2 appears to do it:
<my-multi-select>
<my-option *ngFor="let option of options" [value]="option"></my-option>
</my-multi-select>
...and then within the template for my-multi-select:
<div class=select-container">
<ng-content select="my-option"></ng-content>
</div>
I was content with the transclusion option, but then when I started to actually implement it rand in to difficulty binding the events coming from my-option to my-multi-select. I could try to figure out a way to notify my-select of things that are happening in my-option, like using an Observable, or digging deeper in to using an #Output event -- but that feels like trying to jam a square peg in to a round hole when #Input variables might just be simpler.
This led me to the question, is transclusion even appropriate here? And the bigger question, when is transclusion appropriate, and when is using transclusion jamming a square peg in to a round hole?
For your example, it's simple to compare the two aproaches as you are only including one text item as part of the transclusion. This makes using #Input() trivial and may well be the best solution.
However, imagine a scenario where you have multiple elements you want to include in your child component, each with custom HTML tags along for the ride. Using trasnclusion this is trivial, but using #Input() it requires a few "hacks" to get right and isn't very maintainable or extendable.
Explanation
Building on from this Todd Motto blog about transclusion as a reference, we can utilise transclusion to have more complex HTML for our title and content sections without issue.
Transculsion Parent
<my-component>
<my-title>
<h1>This is the Component title!</h1>
</my-title>
<my-content>
And here's some awesome content.
<ul>
<li>First</li>
<li>Second</li>
</ul>
</my-content>
</my-component>
Transclusion Child
<div class="my-component">
<div>
Title:
<ng-content select="my-title"></ng-content>
</div>
<div>
Content:
<ng-content select="my-content"></ng-content>
</div>
</div>
Now imagine this same scenario using only #Input() to declare our elements.
Our binding from the parent isn't very friendly, we definitely don't want to do this for more complex scenarios.
In our child component we have to use [innerHTML] to get around interpolation encoding in Angular. Again, this isn't very easy to extend or maintain. This is where in my opinion transclusion really excels.
Input Parent
<my-component
[my-title]="<h1>This is the Component title!</h1>"
[my-content]="And here's some awesome content.<ul><li>First</li><li>Second</li></ul>">
</my-component>
Input Child
<div class="my-component">
<div [innerhtml]="'Title:' + my-title"></div>
<div [innerhtml]="'Content:' + my-content"></div>
</div>
This led me to the question, is transclusion even appropriate here?
If want html to look like:
<my-multi-select>
<my-option *ngFor="let option of options" [value]="option"></my-option>
</my-multi-select>
Where my-multi-select and my-option are components, then transclusion is the way to do it.
I am learning Angular and i am ok with the basics however i have a situation where i have 3 interlinking panels or panes. You select the category from the first pane, it populates a list of products in the second pane then choosing one of these populates the third pane with the product details.
I have mocked up the structure here: https://jsfiddle.net/pbpxexsa/1/
Whats the best approach for constructing this?
I added some routing to have a meaningfull url, but i can only have one ng-view.
I have looked at ui.router and this looks like it might fit
Could i just have a separate controller on each pane and an observer to watch for changes but i have read this is to be avoided.
I like the aspect of directives and understand i could provide <product-pane></product-pane> and <product-details></product-details> directives but again not sure how to link them.
The books i am reading don't seem to cover this kind of architecture, am i missing something obvious?
Thanks in advance.
I think you might be over-thinking this slightly. You have 3 panels which are all viewing a single data group, but you are trying to engineer a solution that would only be necessary when each panel is showing different data. You can easily do this design with a single controller.
Just a generic mockup (not working or tested, but should give you an idea):
<div class="category-pane">
<ul>
<li ng-repeat="category in categories"
ng-click="setSelectedCategory(category)">{{category.name}}</li>
</ul>
</div>
<div class="products-pane">
<ul>
<li ng-repeat="product in selectedCategory.products"
ng-click="setSelectedProduct(product)">{{product.name}}</li>
</ul>
</div>
<div class="product-details-pane>
{{selectedProduct.name}}
{{selectedProduct.info}}
....
</div>
In controller:
$scope.setSelectedCategory= function(category){
$scope.selectedCategory = category;
$scope.selectedProduct = null; //remove whatever was in the product pane
};
$scope.setSelectedProduct= function(product){
$scope.selectedProduct = product;
};
I've created a simple directive in Angular which generates a scroller to display some products.
I'm having an issue with one part of the code.
<ul ng-style="{'margin-left':{{currentMargin}}+'px'}">
<li ng-repeat="tyre in tyres" ng-style="{'width':{{liWidth}}+'px'}">
<div class="imageContainer"><img src="../Images/eutl/{{tyre.image}}"/></div>
<div class="details">
<h3>{{tyre.name}}</h3>
About this tire
</div>
</li>
</ul>
and this is what it looks like in the browser once executed
<ul ng-style="{'margin-left':0+'px'}">
<!-- ngRepeat: tyre in tyres -->
<li ng-repeat="tyre in tyres" ng-style="{'width':265+'px'}" class="ng-scope" style="width: 265px;">
<div class="imageContainer"><img src="../Images/eutl/tire.jpg"></div>
<div class="details">
<h3 class="ng-binding">Fuel Efficient</h3>
About this tire
</div>
</li>
<!-- end ngRepeat: tyre in tyres --></ul>
after executing this on my page I get the scroller and the ng-style inside the "li" elements gets displayed correctly, while the ng-style for the "ul" doesn't.
I've tried multiple solutions, even trying to add the same exact ng-style from the "li" element and it just doesn't get processed and no style is added.
Can anyone help me by pointing out a mistake in my markup or a possible cause for one ng-style working on the Li elements and the other not working on the UL itself?
The other problem I'm having is that the value of the currentMargin is not updating in IE8/9 and so on.
Thanks
ng-style accepts an Angular expression that evaluates to an object. This means that if you want to use a variable inside that expression, you can use it directly (without the double-curlies):
ng-style="{width: liWidth + 'px'}"
Double-curlies are used when you want to insert a dynamic, interpolated value to an argument that accepts a string, like <img alt="{{product.name}} logo">. You then put an expression inside those brackets.
Try to do :
ng-style="{'width':liWidth+'px'}">
No curly bracket, a lot of ng directive don't like it
I have this part of HTML
<ul class="list-group" ng-repeat="thing in things">
<li class="list-group-item" ng-bind-html="tmpfcn(thing.text, thing.objects)"></li>
</ul>
Which for every iteration returns some text with hyperlinks inside. e.g.
I like the winter
I want to use qtip for each of the hyperlinks with different content for each hyperlink.
I've defined a qtip directive which I've tested with an HTML element like this
<span qtip="This is the message printed"> hover over me</span>
and it works just fine.
I've looked at many solutions for similar problems like this but I couldn't get my code to work.
Could someone provide some guidelines? An example would bereally helpful as well.
It's more a suspicion than an verified problem but..
I've worked with knockoutjs for a while and there it was a performance issue to create lots of ko click bindings - the better way was to use much fewer jQuery .on('click', ...) to handle these.
Now that I'm diving into angularjs I have a ng-repeat within ng-repeat and inside this second one I have a few buttons with ng-click..
<ul>
<li ng-repeat="el in collection">
<button ng-click="someFn()">click me</button>
<button ng-click="someFn2()">click me</button>
<button ng-click="someFn3(el)">click me</button>
</li>
</ul>
Doesn't this create a lot of click event bindings? Or does angular optimise this somehow?
It's hardly any optimization in this case. What if you have several nested ngRepeats. Against which one should optimization be performed? Not easy to answer indeed. Moreover, repeated items can be controlled by another controller.
I see the following a bit hackish way of accomplishing the task.
We can apply ngClick to the parent element calling some method and passing the value allowing to identify clicked item.
<ul ng-click="itemClicked(itemIdentifier)">
<li ng-repeat="el in collection">
<button>click me</button>
</li>
</ul>
The only question left to answer is how we get this identifying value. We need our own directive to apply to repeated DOM element which attaches this value to the element. After that we can get the value from $event object.
<ul ng-click="itemClicked($event.target.itemIdentifier)">
<li ng-repeat="el in collection">
<button click-optimiztation="el">click me</button>
</li>
</ul>
Sure, you've to check for undefined values.
This approach must be adapted to your needs cause you want to have several clickable elements inside each repeated template. Nonetheless, I hope the idea is clear.