I have an ordered list of items that I want to be displayed. Getting this done with Angular is trivial:
<p>Filter a game: <input type="text" ng-model="search.name" /></p>
<game-card ng-repeat="game in games | filter:search" game ="game" size="large"></game-card>
This outputs an ordered list of games that has been sorted from the database. However, I want to show a few snippets of arbitrary content inside this ng-repeat. This diagram below indicates what I'm after. The lefthand side shows how it should look in the unfiltered state, the righthand side if a search term as present.
As you can see, I want to highlight the first item with a "Next up" heading, and following that, an "After that" heading. When a search is present, I don't want these headings at all as they won't be relevant.
I'm completely new to Angular ("you lost me at service" new, by the way), but have come from a Knockout.js background. What is the "Angular way" of solving this?
Stack up ng-if's on the headings and compare with $index? Does $index even care about the filter being applied?
Kind of dirty solution: within the ng-repeated element, put something like
<div ng-if="$index === 0">
<h2>Next up</h2>
</div>
<div ng-if="$index === 1">
<h2>After that</h2>
</div>
<div>
<!-- Your content goes here, unconditionally -->
</div>
And the obvious problem with this is that two tests are performed for each "instantiation" of the content of the ng-repeated element, and they are useless except for the first and second.
Otherwise you could place your first two bits of contents with headers outside the ng-repeat and then start repeating from index 2, but I'm not sure that's possible with ng-repeat.
If it's not, then split your data into firstElement, secondElement and rest where rest is Array.prototype.slice.call(/* your initial list of elements */, 2).
The problem with this last solution is that it requires you to adapt your data to how you present it, which is undesirable for obvious reasons.
edit: OK here's something better, not tested:
<div ng-if="elements.length > 0">
<h2>Next up</h2>
<div><!-- First content goes here, it uses elements[0] --></div>
</div>
<div ng-if="elements.length > 1">
<h2>After that</h2>
<div><!-- Second content goes here, it uses elements[1] --></div>
</div>
<div ng-repeat="element in elements.slice(2)">
<!-- The rest goes here, unconditionally -->
</div>
Instead of doing your ng-repeat in the game-card tag, do it in a wrapping div and then use ng-repeat special variables:
<p>Filter a game: <input type="text" ng-model="search.name" /></p>
<div ng-repeat="game in games | filter:search">
<div ng-if="$first">NEXT UP</div>
<div ng-if="$index === 1">AFTER THAT</div>
<game-card game="game" size="large"></game-card>
</div>
My solution was to ng-show the titles only if the length of the results passed through the filter equaled the total number of games:
<h2 ng-show="games.indexOf(game) == 1 && search.length == games.length">More Games</h2>
Related
So, I'm trying to use a angular way to send the buttons created to either the #start or #finish divs, based on click on the buttons selected, so in a way they would send themselves if you may if they follow a condition, which in this case is to be either inside of the #start or #finish divs. With Jquery, I just check what's the parent of certain element, if matches one, I send it to the other, and vice versa. Now with angular, I have been looking into the whole, rendering and stuff, but my head can't just understand the overall picture, and I even though I was able to click and send the element clicked to a different div, I couldn't do it with the other buttons created, and also with the button that was first clicked, in the other div.
<div class="ui raised very padded text container segment"
#finish>
</div>
<div class="ui raised very padded text container segment" #start>
<button
*ngFor='let word of ge_array'
(click)="goToNext()"
>{{word}}</button>
</div>
Does anybody know how to tackle this situation?
Renan, I would change all your plan. I have an array of element. this elements have a property "place". I will show in two div one for "plan1" and the other for "plan2".
//the .ts is like
items:any[]=[{word:'uno',place:1},{word:'dos',place:1},{word:'tres',place:2}]
get items1(){
return this.items.filter(it=>it.place==1);
}
get items2(){
return this.items.filter(it=>it.place==2);
}
//the .hmtl like
<h2>place 1</h2>
<button *ngFor="let item of items1" (click)="item.place=2">{{item.word}}</button>
<h2>place 2</h2>
<button *ngFor="let item of items2" (click)="item.place=1">{{item.word}}</button>
I would prefer using a getter and have "item1" and "item2". the click make that the property "place" of the item becomes 2 in place 1 and becomes 1 in place2
You can make two *ngFor over the same array also using a *ngIf like
<h1>place 1</h1>
<div *ngFor="let item of items">
<button *ngIf="item.place==1" (click)="item.place=2">{{item.word}}</button>
</div>
<h1>place 2</h1>
<div *ngFor="let item of items">
<button *ngIf="item.place==2" (click)="item.place=1">{{item.word}}</button>
</div>
and forget the getters
I am playing around with a card game app, and I want a card image to show for each instance that is in the hand array. To keep it simple, I am looking to show card1.png if the array looks like [1,2,3,4,5]. However, I want the image to show twice if the array looks like [1,1,3,4,5]. So far, I can just get it to show once.
Index.html
<div ng-repeat="hand in hands track by $index">
Player {{$index}} hand: {{ hand }}
<img src="/images/faces-one.png" ng-show="numberInArray(hand, 1)">
<img src="/images/two.png" ng-show="numberInArray(hand, 2)">
<img src="/images/three.png" ng-show="numberInArray(hand, 3)">
<img src="/images/four.png" ng-show="numberInArray(hand, 4)">
<img src="/images/five.png" ng-show="numberInArray(hand, 5)">
<img src="/images/six.png" ng-show="numberInArray(hand, 6)">
</div>
App.js
$scope.numberInArray = function(hand, num) {
var found;
found = hand.includes(num) ? true : false;
return found;
}
I'm not suggesting that this is the best solution, but to fix what you've currently got (or at least appear to have)...
Use another (nested) ng-repeat on each image instead of ng-show. You should be able to attach it to a getNunbersInArray(n) function that returns a subset of the main array that can then be looped through the desired number of times.
For example, if you have [1,1,3,4,5] in your main array, the function connected to the new ng-repeats would return [1,1] (when passed 1) and [2] (when passed 2), etc.
You don't need to do anything with the values returned in these new arrays, the only thing that matters is that they contain the correct number of elements to ensure that the image is repeated the correct number of times.
Following the comments I received, this is the solution I implemented:
Index.html
<div ng-repeat="hand in hands track by $index">
Player {{$index}} hand:
<div ng-repeat="card in hand track by $index">
<img ng-src="/images/{{card}}.png">
</div>
</div>
The numberInArray function was removed as it was not required. Basically, This now iterates over the array of arrays, and displays the image found at the ng-src, which in this case would be 1.png, 2.png etc. This achieves my goal of showing the card image for each number in the array, regardless of its count.
I have an odd issue that is driving me crazy
I basically have a list of article of different type: news, tweets and video
I am rendering them like this:
<div ng-repeat="item in items | orderBy:-timestamp track by item.id" ng-switch="item.type">
<?php
include("templates/ang-youtube.html");
include("templates/ang-tweet.html");
include("templates/ang-news.html");
?>
</div>
Inside each include i am doing something like this:
<div class="item" masonry-brick ng-switch-when="news">
...content in here
</div>
or
<div class="item" masonry-brick ng-switch-when="tweet">
...content in here
</div>
or
<div class="item" masonry-brick ng-switch-when="youtube">
...content in here
</div>
Now the problem is the youtube items are always rendered first. The order of the items (timestamp) is ignored. The tweets and news items though render correctly.
If i remove the switch and just list out the items as text then everything is in the correct order. As soon as I add the switch the youtube items are rendered first again.
Any ideas what I am doing wrong?
I have tried moving the switch inside the ng-repeater like so:
<div ng-repeat="item in items | orderBy:-timestamp track by item.id">
<div ng-switch="item.type">
<?php
include("templates/ang-youtube.html");
include("templates/ang-tweet.html");
include("templates/ang-news.html");
?>
</div>
</div>
But it makes no difference. The youtube items are always rendered first.
If i inspect the $scope.items array they are ordered correctly by timestamp.
I have also tried using an ng-if instead of a switch but get the same outcome
Any ideas? :-(
Also tried using ng-include like this:
<div ng-include="'templates/ang-{{item.type}}.html'"></div>
But that doesn't work
DOH!!! Needed to add the preserve order attribute as outlined here
https://github.com/passy/angular-masonry
I am using this: http://angular-ui.github.io/ui-utils/ and to be more specific this:https://github.com/angular-ui/ui-utils/blob/master/modules/scroll/README.md
however it does not seem to work. Here is an example:
<div ng-scroll-viewport style="height:240px;" class="chat-messages">
<div class="chat-message" ng-scroll="chat in chats">
<div ng-class="$index % 2 == 0? 'sender pull-right' : 'sender pull-left'">
<div class="icon">
<img src="{{chat.img}}" class="img-circle" alt="">
</div>
<div class="time">
{{chat.time}}
</div>
</div>
<div ng-class="$index % 2 == 0? 'chat-message-body on-left' : 'chat-message-body'">
<span class="arrow"></span>
<div class="sender">{{chat.name}}</div>
<div class="text">
{{chat.msg}}
</div>
</div>
</div>
</div>
But All I get in HTML is this :
<div class="chat">
<div class="chat-messages" style="height:240px;" ng-scroll-viewport="">
<!--
ngScroll: chat in chats
-->
</div>
If I replace ng-scroll with ng-repeat, it works perfectly. But chats need scroll bars, so... How can I get one? :)
The documentation of ngScroll directive had also tricked me into simply replacing ng-repeat by ng-scroll. Unfortunately, it turned out not as simple as that, see also the small, working example at http://plnkr.co/edit/fWhe4vBL6pevcuLutGC4 .
Note that
"datasource" (or whatever object you want to iterate over for the contents of the scroll list) must implement a method "get(index,count,success)" that calls success(results), see hXXps://github.com/angular-ui/ui-utils/blob/master/modules/scroll/README.md#data-source
The array must have exactly count elements. Otherwise, no scroll window/bar will ever show, which can be very irritating!
Although UI.Utils says it has no external dependencies, ui.scroll has actually a dependency on either ui.scroll.jqlite, or jQuery. So make sure to list both ui.scroll and ui.scroll.jqlite in your module definition which contains the controller with datasource object (and load their .js files, or load ui-utils.js which contains both), see https://github.com/angular-ui/ui-utils/blob/master/modules/scroll/README.md#dependencies
Be careful when your server is sending some Content Security Policies (CSP). Maybe turn them off while trying to get ng-scroll to work first, then re-apply CSP and tune the policies accordingly for ui.scroll to work.
One way of getting a scroll is to use CSS, set overflow-y to scroll and you will get scroll bar.
If you need to scroll to the bottom, play with anchorScroll
http://docs.angularjs.org/api/ng.$anchorScroll.
I'm trying to deal with the issue of scope inside of an ng-repeat loop - I've browsed quite a few questions but have not quite been able to get my code to work.
Controller code:
function Ctrl($scope) {
$scope.lines = [{text: 'res1'}, {text:'res2'}];
}
View:
<div ng-app>
<div ng-controller="Ctrl">
<div ng-repeat="line in lines">
<div class="preview">{{text}}{{$index}}</div>
</div>
<div ng-repeat="line in lines">
<-- typing here should auto update it's preview above -->
<input value="{{line.text}}" ng-model="text{{$index}}"/>
<!-- many other fields here that will also affect the preview -->
</div>
</div>
</div>
Here's a fiddle: http://jsfiddle.net/cyberwombat/zqTah/
Basically I have an object (it's a flyer generator) which contains multiple lines of text. Each line of text can be tweaked by the user (text, font, size, color, etc) and I want to create a preview for it. The example above only shows the input field to enter text and I would like that to automatically/as-you-type update the preview div but there will be many more controls.
I am also not sure I got the code right for the looping index - is that the best way to create a ng-model name inside the loop?
For each iteration of the ng-repeat loop, line is a reference to an object in your array. Therefore, to preview the value, use {{line.text}}.
Similarly, to databind to the text, databind to the same: ng-model="line.text". You don't need to use value when using ng-model (actually you shouldn't).
Fiddle.
For a more in-depth look at scopes and ng-repeat, see What are the nuances of scope prototypal / prototypical inheritance in AngularJS?, section ng-repeat.
<h4>Order List</h4>
<ul>
<li ng-repeat="val in filter_option.order">
<span>
<input title="{{filter_option.order_name[$index]}}" type="radio" ng-model="filter_param.order_option" ng-value="'{{val}}'" />
{{filter_option.order_name[$index]}}
</span>
<select title="" ng-model="filter_param[val]">
<option value="asc">Asc</option>
<option value="desc">Desc</option>
</select>
</li>
</ul>