Getting a matching element from a different array - angularjs

I have an array called groupUsers and another array within an array called tasks.users which look like this:
this.groupUsers = [
{$id: "simpleLogin:1", name: "bob"},
{$id: "simpleLogin:2", name: "joe"}];
this.tasks = [
{title: "do something", users:["simpleLogin:1","simpleLogin:2"]},
{title: "do something else", users:["simpleLogin:2"]}];
What I am trying to do is to get the user's name from groupUsers to display when I show the tasks rather than their id, but I am unsure in how to go about getting it. I assume it involves a filter?
With the following code I can display the tasks and the user id's assigned to each task, just not the names.
<div class="taskContainer" ng-repeat="task in todo.tasks | orderBy:'-when'">
<div><span ng-repeat="users in task.users">{{users}} </span></div>
<span> {{task.title}} </span>
</div>

You can expand to this function to fit your needs, right now it will return one name of the passed in id, $filter is an angular-service, remember to include it in your controller
$scope.getName = function(id){
return $filter('filter')(groupUsers, { name: id})[0].name;
};
here is your html
<div class="taskContainer" ng-repeat="task in todo.tasks | orderBy:'-when'">
<div><span ng-repeat="users in task.users">{{getName(users[0])}} </span></div>
<span> {{task.title}} </span>
</div>

Related

Alternative to breaking a loop in Angular while iterating through an array?

I have read online that it is impossible to break *ngFor in Angular but was wondering if anyone has an alternative to this? I have been stuck on this for days on end.
I would like to loop a list all animal names for the user and when the user clicks a specific animal name, info appears below the button they have just clicked.
Please see the example below:
<div ="container" *ngFor="let animal of (this.animals || [])">
<h5 (click)="getAnimalInfo(animal.animalName)">{{animal.animalName}}</h5>
<ng-container *ngIf="getAnimalInfo(animal.animalName)">
<div class="container" *ngFor="let animalInfo of (this.animalInfo || []);">
<li {{animalInfo.title}} </li><br>
<li {{animalInfo.paragraph}} </li><br>
</div>
</ng-container>
</div>
Currently, when I click an animal name item my original list, it will display the details below every single animal name on the list instead of just the one I have clicked on.
How can I fix this so that the loop knows to break when I select one animal name?
N.B I am aware that if i take my second for loop out of the original for loop, this can work, however I want the data to populate directly under the original button and be toggle-able rather than having the info appear at the very bottom of the full list.
I don't know how to break loop in *ngFor loop but for you requirement i have given the solution below.
First of all i have defined dummy animals array and animalInfo array in ts file. You can replace with your original array.
animals = [{ animalName: 'dog' }, { animalName: 'cat' }];
animalInfo = [{ paragraph: 'dog paragraph', title: 'dog' }, { paragraph: 'cat paragraph', title: 'cat' }];
then i have declared selectedAnimalInfo which we can use to store the click animal info.
selectedAnimalInfo = {};
I am expecting animalName parameter in animal array is same as title parameter of animalInfo arrar.
After that your getAnimalInfo method will be look like below.
getAnimalInfo(animalName: string) {
this.selectedAnimalInfo = {
[animalName]: this.animalInfo.find((animal) => animal.title === animalName)
};
}
And at the end html code
<div class="container" *ngFor="let animal of (this.animals || [])">
<h5 (click)="getAnimalInfo(animal.animalName)">{{animal.animalName}}</h5>
<ng-container *ngIf="selectedAnimalInfo[animal.animalName]">
<div class="container">
<li> {{selectedAnimalInfo[animal.animalName].title}} </li><br>
<li> {{selectedAnimalInfo[animal.animalName].paragraph}} </li><br>
</div>
</ng-container>
</div>
Hope it will full fill you requirement.

AngularJS when I click a link I want to filter an array

I'm currently working with a website using PHP and AngularJS, I have a product list and a droplist that has some links which will allow the user to filter products by type(electric guitars, acoustic guitars, etc...) is there any way to do a function that filters the ng-repeat to only show products whose type are the selected one?
This is what I currently have:
<article class="producto" ng-repeat="product in dbproducts | filter:prodsearch">
<div class="img">
<img src="products/img/{{product.Imagen}}" alt="{{product.Marca + ' ' + product.Modelo}}">
<p class="product-model">{{product.Marca + ' ' + product.Modelo}}</p>
<span class="product-price">{{product.Precio | currency: '€'}}</span>
</div>
<div class="check">
<div class="paygroup">
<p class="price" ng-bind="product.Precio | currency: '€'"></p>
<button class="buy"><i class="fa fa-shopping-cart"></i>{{'BUTTON_BUY' | translate}}</button>
<p>{{'BUTTON_AVAILABLE' |translate}}: {{product.Unidades_disponibles}} </p>
</div>
</div>
<div class="txt">
<p>Test, hola, {{ $index }}</p>
</div>
<span class="close fa fa-times fa-inverse"></span>
</article>
So If I get your code right, you already have a pipe filter in your ng-repeat:
| filter:prodsearch
So I guess somewhere in an input directly or in your controller you hare binding a user input to the variable "prodsearch" and the ng-repeat is live filtered.
In the same way, you can have buttons, whether they are in a droplist or not, which click action would be assigning a certain text to the "prodsearch" variable.
A filter is what you want. Another way is Using a copy array of products to show and writing a function which update your array of products. This function receives a type and just add related products to showing items.
Something like this:
Controller
Call this function on ng-change of drop down
$scope.filters = ["all", "electric", "guitars", "acoustic", "guitars"];
$scope.selectedFilter = "all";
$scope.filterProds() {
if ($scope.selectedFilter == "all") {
$scope.dbproductsToShow = $scope.dbproducts;
} else {
$scope.dbproductsToShow = [];
$scope.dbproducts.forEach(function(itm) {
if (itm['type'] == filter) {
$scope.dbproductsToShow.push(itm);
}
});
}
}
And in your Html
<select ng-model="selectedFilter" ng-options="filter in filters" ng-change="filterProds()"></select>
<article class="producto" ng-repeat="product in dbproductsToShow">
I've fixed it, what I did was:
Adding another pipe in ng-repeat called filter: producttype
Added a $scope.producttypevariable;
Changed the value of the variable each time I clicked on the links in the dropdown so it would filter by type. This:
<li ng-click="producttype='electrica'"><a>Guitarras eléctricas</a></li>
Thanks for your help and tips, and sorry if this method is not the best, I learnt angularjs by myself(i'm not a good self-learner).

ng-repeat multiple value pairs on single element using angular-isotope

I'm using the angular-isotope plugin to repeat multiple quotes in a masonry grid. My goal is to add multiple unique tags to each quote with each tag having its own unique filter class so that when you click the tag, only tags from that filter appear. Currently, the functionality works fine with just a single tag:
HTML:
<div class="quotes js-masonry" isotope-container="isotope-container">
<div class="quote-col {{item.class}}" ng-repeat="item in quotes" isotope-item="isotope-item">
<div class="quote-single">
<div class="quote-quote">"{{item.quote}}"</div>
<div class="quote-author">
<a class="filter-click" data-filter="{{item.dataFilter}}">- {{item.author}}</a>
</div>
<div class="quote-bottom clearfix">
<span class="quote-share">
<img src="img/icons/facebook.png">
<img src="img/icons/twitter.png">
</span>
<span class="quote-tags" ng-repeat="?">
<a class="filter-click" data-filter="{{item.tagFilter}}">{{item.tag}}</a>
</span>
</div>
</div>
</div>
Angular Controller:
var demo = angular.module('angular-isotope-demo', []);
demo.controller('QuoteController', function QuoteController($scope) {
$scope.quotes = [
{
"quote":"It is better to lead from behind and to put others in front, especially when you celebrate victory when nice things occur. You take the front line when there is danger. Then people will appreciate your leadership.",
"author":"Nelson Mandela",
"tag": [
"Leadership",
"Second Class"
],
"class":"mandela leadership",
"dataFilter": ".mandela",
"tagFilter": [
".leadership",
".second-class"
]
}
]
});
My question is, how do I repeat that item tag (i.e. "Leadership", "Second Class") and attach it to that ".second-class" in the tagFilter so it looks like this?:
I hope this is clear to understand.
You will likely have a nested ng-repeat. So turn your quote.tag element into an array and do something like this:
<div ng-repeat="tag in item.tags">
<a class="filter-click" data-filter="{{tag.filter}}">{{tag.tag}}</a>
{{ $last ? '' : ( $index < item.tags.length-2 ) ? ', ' : '' }}
</div>
If you wish to have different classes for each item, then I would have another element within item.tags, say item.tags.classes and then do this:
<a class="filter-click" ng-class="tag.classes" data-filter="{{tag.filter}}">{{tag.tag}}</a>
Your data will need to be restructured for this to work:
{
"quote":"It is better to lead from behind and to put others in front, especially when you celebrate victory when nice things occur. You take the front line when there is danger. Then people will appreciate your leadership.",
"author":"Nelson Mandela",
"tags": [
{
"tag":"Leadership",
"filter":".leadership",
"classes": "second-class"
}
],
"class":"mandela leadership",
"dataFilter": ".mandela",
}

LocalStorage of contenteditable content (AngularJS)

I have a little AngularJS tool that inserts cards. My goal is to store them locally. I worked that out for the cards array, but not for the card content, that is "contenteditable"
Can u help me with this and give me some best practice solutions?
Here's a Plunker (in JS) for that (the red big button deletes the localStorage. Be sure to have a wide window open): http://plnkr.co/edit/SlbWZ5Bh62MDKWViUsMr
This is my code (with CoffeeScript. For JS see the Plunker above):
This is the markup for the user input
<div class="card card-{{ card.color }}">
<header>
<p class="points" contenteditable></p>
<p class="number" contenteditable>#</p>
<h2 class="customerName" contenteditable>{{ card.customer.name }}</h2>
<h3 class="projectName" contenteditable>Project Name</h3>
</header>
<article>
<h1 class="task" contenteditable>Title</h1>
<p class="story" contenteditable>Description</p>
</article>
<footer>
<div class="divisions">
<p class="division"></p>
<button ng-click="deleteCard()" class="delete">X</button>
</div>
</footer>
</div>
<div class="card card-{{ card.color }} backside">
<article>
<h2 class="requirement">Requirements</h2>
<p contenteditable></p>
</article>
</div>
Here you see my localStorage setup for the cards array that was in my controller above:
Card = (#color, #customer) ->
$scope.cards = []
json = localStorage.getItem "cards"
getCards = JSON.parse(json) if json?
$scope.cards = $scope.cards.concat getCards if getCards?
$scope.reset = ->
localStorage.clear()
$scope.save = ->
cards = []
for card in $scope.cards
cards.push
color: card.color
customer:
name: card.customer.name
localStorage.setItem "cards", JSON.stringify(cards)
$scope.addCardRed = (customer) ->
$scope.cards.push new Card("red", customer)
$scope.save()
How do I store the user inputs in the different fields in localStorage? I heards something about serialization, but I don't know what it means in my case!
Thank you so much in advance!
You can use the ng-model directive with any contenteditable field, just like you would do with an input or textarea. so, instead of trying to use the {{...}} braces to bind your model to your view, you should just use ng-model, and then just treat your editable DOM elements as if they were fields in a form.
For example, in your view :
<header ng-repeat="card in cards">
<!-- These are just sample fields for the card object, but you can modify them -->
<p class="points" contenteditable ng-model="card.points"></p>
<p class="number" contenteditable ng-model="card.number"></p>
<h2 class="customerName" contenteditable ng-model="card.customer.name"></h2>
<h3 class="projectName" contenteditable ng-model="card.projectName"></h3>
</header>
And then in your controller you would attach the model to the $scope as you've already done using $scope.cards = $scope.cards.concat getCards if getCards?. This will two-way bind your cards model to your controller's scope.
And then in your controller, to mirror the model data in LocalStorage, you can do it yourself using something like this:
In your controller:
....
// for example's sake
$scope.cards = [ // the cards array would look something like this
{points: 0, number: 5, customer: {name: 'bob'}, projectName: 'myProj1'},
{points: 1, number: 6, customer: {name: 'joe'}, projectName: 'myProj2'},
{points: 2, number: 7, customer: {name: 'bill'}, projectName: 'myProj3'},
{points: 3, number: 8, customer: {name: 'jerry'}, projectName: 'myProj4'}
];
....
// listen to all changes to the $scope.cards array
$scope.$watch('cards', function(newVal){
var str = angular.toJson(newVal); // serialize the cards array into a string
// NOTE: the 'someKey' string is the key that you'll use later to retrieve it back
window.localStorage['someKey'] = str; // store the serialized string in localStorage
}, true);
....
In the above example, the angular.toJson(newVal) will take the newVal variable (which is just a reference to the "recently updated" cards array), and will serialize it into JSON string (ie angular.toJson just basically wraps the native JSON.stringify() method). In order to put a javascript object into LocalStorage it must be serialized to a string because you can only put primitives as the value in a LocalStorage key.
So the newVal will get put into localStorage looking something like this:
"[{"points":0,"number":5,"customer":{"name":"bob"},"projectName":"myProj1"},{"points":1,"number":6,"customer":{"name":"joe"},"projectName":"myProj2"},{"points":2,"number":7,"customer":{"name":"bill"},"projectName":"myProj3"},{"points":3,"number":8,"customer":{"name":"jerry"},"projectName":"myProj4"}]"
And then later (whenever you need it) you can retrieve the cards array from localStorage again using the following:
var str = window.localStorage['someKey'];
$scope.cards = angular.fromJson(str);
Or you can use a library to do the serialization/saving like this one: https://github.com/grevory/angular-local-storage. I've never used it but it does exactly what you want it to.
Hopefully that helps clarify things a bit.
UPDATE: This is beyond the scope of this question, but since you asked. It sounds like your'e not grasping the concept of the ng-repeat and ng-model directives. These are probably the two most famous (and most widely used) directives in Angular. By combining these two directives (as in the view example above), it will automatically keep your model (ie $scope.cards) and your view (ie <header>) in sync as users edit the data in your view. ng-repeat will "automagically" create a new header element for every card in your cards array (hence the ng-repeat="card in cards"). So as new cards get added or cards are removed from the cards array, Angular will add or remove <header> elements as needed. Then, using the contenteditable and ng-model directives, Angular will bind the content of those editable DOM elements to the values for each card. Typically ng-model is used with form elements (ie inputs/textareas/selects) but it can also be used for any editable field. Think of your editable elements as if they were an input, and then take a good long look at the <input ng-model="" /> example from the angular docs found here. That should help.
Now I know what confused me!
The Two-Way-Binding of my card properties didn't work with contenteditable. I removed each contenteditable attribute and instead of p, h2 and so on I replaced the tags with input tags. That worked fine for me.
Thank you for your patience and your great explanations, #tennisagent! And for the $watch hint! I am really grateful for your help :)
That's my solution for the Controller:
angular.controller 'CustomerController', ($scope) ->
$scope.customers = [
{ name: "name1" }
{ name: "name2" }
{ name: "name3" }
]
$scope.cards = []
Card = (#color, #customer, #points, #number, #projectName, #story) ->
$scope.$watch 'cards', ((newValue) ->
string = angular.toJson(newValue)
localStorage["cards"] = string
), true
json = localStorage["cards"]
parsedCards = angular.fromJson(json) if json?
$scope.cards = $scope.cards.concat parsedCards if parsedCards?
$scope.reset = ->
localStorage.clear()
sessionStorage.clear()
$scope.cards = []
$scope.addCardRed = (customer) ->
$scope.cards.push new Card("red", customer)
That's my solution for the Markup:
<div class="card card-{{ card.color }}">
<header>
<input class="points" contenteditable ng-model="card.points"></input>
<input class="number" placeholder="#" contenteditable ng-model="card.number"></input>
<input class="customerName" contenteditable ng-model="card.customer.name"></input>
<input class="projectName" placeholder="Projekt" contenteditable ng-model="card.projectName"></input>
</header>
<article>
<input class="task" placeholder="Titel" contenteditable ng-model="card.task"></input>
<textarea class="story" placeholder="Story" contenteditable ng-model="card.story"></textarea>
</article>
<footer>
<div class="divisions">
<p class="division"></p>
<button ng-click="deleteCard()" class="delete">X</button>
</div>
</footer>
</div>
<div class="card card-{{ card.color }} backside">
<article>
<h2 class="requirement">Requirements</h2>
<textarea class="requirements" placeholder="Aspects" contenteditable ng-model="card.requirements"></textarea>
</article>
</div>

Filtering an ng-repeat list based on a sub-object property

jsfiddle http://jsfiddle.net/KfSBq/
By sub-object I mean that the objects I am displaying with ng-repeat all contain a list of objects within themselves, and I am looking to filter based on the property of one of those sub-objects.
This alone was quite straightforward to do. I have an object of dailies, each containing a date and an entries list of objects:
function Ctrl($scope) {
$scope.dailies = [{date: new Date('07/07/2013'),
entries: [{category: 'A', note:'Lorem ipsum'},
{category: 'B', note: 'Lorem ipsum'}]},
{date: new Date('05/02/2013'),
entries: [{category: 'A', note: 'Lorem ipsum'}]}];
}
I display them, filtering by category:
<div ng-controller="Ctrl">
<div class="daily" ng-repeat="daily in dailies | orderBy:'-date' ">
{{ daily.date | date:'dd/MM/y' }}
<div class="entry" ng-repeat="entry in daily.entries | filter:{ category: 'B'} ">
<span>{{ entry.category }}</span>, <span>{{ entry.note }}</span>
</div>
</div>
</div>
My issue here is that the daily objects that now contain no entries at all are still displayed. How do I achieve a situation where, if the filtering makes the entries list of a daily empty, that daily is not displayed either?
You are allowed to create new scope members inside the expressions.
This lets you assign a filtered list to a new variable, which can be used throughout the local scope. With that, you can pass the length of the filtered list to ng-show:
<body ng-app>
<div ng-controller="Ctrl">
<div class="daily"
ng-repeat="daily in dailies | orderBy:'-date' "
ng-show="filteredEntries.length"
>
{{ daily.date | date:'dd/MM/y' }}
<div class="entry"
ng-repeat="entry in filteredEntries = (daily.entries | filter:{ category: 'B'})"
>
<span>{{ entry.category }}</span>, <span>{{ entry.note }}</span>
</div>
</div>
</div>
</body>
FIDDLE
Btw, nicely put question!

Resources