ng-repeat function called multiple times - angularjs

I have written ng-repeat like below
<div class="anchor" ng-repeat="race in races" ng-class="showdata (race)"></div>
$scope.showdata = function(data)
{
var raceId = data.raceId;
if(raceId == 66666666)
{
console.log(data);
console.log('----- next ---')
}
and data is something like this
[
{
"raceId": "434346"
},
{
"raceId": "123456"
},
{
"raceId": "222222"
} ,
{
"raceId": "5555555"
}
,
{
"raceId": "32423443"
}
,
{
"raceId": "66666666"
}
]
Ideally data should get printed only once as raceId 66666666 is only once. But i see object printed thrice. Is there any problem with it. Ideally it means it got called thrice with same data?

On a side note from the comment by #tymeJV, which is clearly correct.
If you are looking to set a particular class based on one of the ng-repeat items, you can also use basic logic inside the ng-class itself.
For example:
<div class="anchor" ng-repeat="race in races" ng-class="{'some-class': race.raceId === '66666666'"></div>
This will only apply the some-class to the race item you have listed in your code.

Related

Trying to filter array with Set. Any suggestions?

I have an array which is used to store tags. It comes with 4 pre-defined tags that users can enter, and then users are also able to create their own tags which are stored in the array. I would like to be sure that the array does not display duplicate strings. Eg. if the user picks one of the pre-defined strings and saves it to the database, then that string is going to appear twice in the array. I have tried to filter it with Set in the Created hook, but it does not do anything.
Do you have suggestions on how I can get this filter to work?
<template>
<v-select
:value="value"
#input="$emit('input', $event)"
:options="options"
maxlength="250"
taggable
multiple
select-on-blur
v-bind="$attrs"
/>
</template>
<script>
import {propertyService} from '#/services/property';
export default {
props: {
value: Array
},
computed: {
options() {
return [
this.$t('aaa'),
this.$t('bbb'),
this.$t('ccc'),
this.$t('ddd'),
...this.dbOptions]
},
},
data() {
return {
dbOptions: [],
};
},
created() {
propertyService.getPropertyTags().then(result => this.dbOptions = result.data);
this.options = [...new Set(this.options)]
}
};
</script>

Reading child of an automated unique ID in a nested ng-repeat

How do I get data of a child of an automated unique ID in a nested ng-repeat, if the ID is not in the first level?
My firebase data set is structured as such (note: I do not know the amount or values of users, dates or unique IDs in that data set):
John:[
10-12-2015: {
"-JcFXid1A2G8EM7A_kwc": {
"description" : "hi",
"timestamp": "1449751857810"},
"-JcFZP5FNtL4Yj6nja_7": {
"description" : "this",
"timestamp": "144975185345"},
},
11-12-2015: {
"-JcFtGoZL7J-CCIjTYcL": {
"description" : "is",
"timestamp": "14497518513715"},
"-JcFXid1A2G8EM7A_kwc": {
"description" : "me",
"timestamp": "1449751846956"},
},
]
Thomas: [ ... ]
My HTML code is:
<ion-list>
<ul class="list">
<li ng-repeat="day in list">
<ion-item class="item-divider">{{day.$id}}</ion-item>
// That works as my output is "10-12-2015, etc"
<li ng-repeat="key in day">
// This does not work
<ion-item>{{key.description}}</ion-item>
</li>
</li>
</ul>
</ion-list>
I am new to Firebase and Angular so my apologies if I am missing something obvious. I also understand that Firebase recommends flat hierarchies, but sorting it by user and date is important for data analysis I need to do. But I would be open to suggestions.
FYI - My controller looks like this:
.controller('summaryCtrl', ['$scope','$state','$firebase','$firebaseArray','SessionData', '$ionicPopup', function($scope,$state,$firebase,$firebaseArray,SessionData,$ionicPopup){
var firebaseObj = new Firebase("https://id.firebaseio.com/" + userid);
var fbArray = $firebaseArray(firebaseObj);
fbArray.$loaded()
.then(function(){
$scope.list = fbArray;
})
}])
Thanks.
You don't need to use .$loaded(), the $firebaseArray will take care of triggering $digest when the data is loaded. The .$loaded() promise is useful for resolving in routes.
In your case though you need to go down into the day key to create the array. Try a data structure like below:
{
"userMessages": {
"$uid": {
"$message_id": {
"timestamp": 1450048003760 // use a timestamp
}
}
}
}
This way you can get messages per user by: /userMessages/$uid/. Then using a query you can get them by date.
You can create factory to simplify this.
Don't mind the code structure below, it uses the Angular Styleguide.
angular.module('app', ['firebase'])
.constant('FirebaseUrl', '<my-firebase-app>')
.service('rootRef', ['FirebaseUrl', Firebase])
.factory('messages', Messages)
.controller('SummaryCtrl', SummaryCtrl);
function Messages(rootRef, $firebaseArray) {
return function Messages(userid, date) {
// turn the date into a timestamp
var timestamp = date.getTime();
var messagesRef = rootRef.child('userMessages').child(userid);
// query for messages on that day
var query = messagesRef.orderByChild('timestamp').endAt(timestamp);
return $firebaseArray(query);
};
}
function SummaryCtrl($scope, messages, rootRef) {
var user = rootRef.getUser()
var today = new Date();
$scope.messages = messages(user.uid, today);
}

Clicking an element inside repeater when another element equals specific input with protractor and jasmine

My HTML structure is this:
<li ng-repeat="m in members">
<div class="col-name">{{m.name}}</div>
<div class="col-trash">
<div class="trash-button"></div>
</div>
</li>
What I want to be able to do is using protractor, click on the trash when m.name equals a specific value.
I've tried things like:
element.all(by.repeater('m in members')).count().then(function (number) {
for (var i = 0 ; i < number ; i++) {
var result = element.all(by.repeater('m in members').row(i));
result.get(0).element(by.binding('m.name')).getAttribute('value').then(function (name) {
if (name == 'John') {
result.get(0).element(by.className('trash-button')).click();
};
});
};
});
This seems like it should work, however, it seems like my function does not even run this.
I've also looked into promises and filters though have not been successful with those either. Keep getting errors.
var array = element.all(by.repeater('m in members'));
array.filter(function (guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (selected) {
selected.element(by.className('trash-button')).click()
}
All I would like to do is click on the corresponding trash when looking for a specific member list!
Any help would be much appreciated.
EDIT: suggested I use xpath once I find the correct element, which is fine, the problem is I cannot get the filter function's promise to let me use element(by.xpath...)
If I try:
var array = element.all(by.repeater('m in members'));
array.filter(function (guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (selected) {
selected.element(by.xpath('following-sibling::div/div')).click()
}
I get the error:
Failed: Object has no method 'element'
Figured it out. Following the Protractor filter guideline in the API reference and using alecxe's xpath recommendation, this code will click on any element you find after filtering it.
element.all(by.className('col-name')).filter(function (member, index) {
return member.getText().then(function (text) {
return member === 'John';
});
}).then( function (elements) {
elements[0].element(by.xpath('following-sibling::div/div/')).click();
});
You can change the xpath to select different stuff incase your HTML does not look like mine.
It looks like you can avoid searching by repeater, and use by.binding directly. Once you found the value you are interested in, get the following sibling and click it:
element.all(by.binding('m.name')).then(function(elements) {
elements.filter(function(guy) {
guy.getText().then(function (text) {
return text == 'John';
})
}).then(function (m) {
m.element(by.xpath('following-sibling::div/div')).click();
});
});
Or, a pure xpath approach could be:
element(by.xpath('//li/div[#class="col-name" and .="John"]/following-sibling::div/div')).click();

AngularJS: list all form errors

Background:
I am currently working on an application with tabs; and I'd like to list the fields / sections that fail validation, to direct the user to look for errors in the right tab.
So I tried to leverage form.$error to do so; yet I don't fully get it working.
If validation errors occur inside a ng-repeat, e.g.:
<div ng-repeat="url in urls" ng-form="form">
<input name="inumber" required ng-model="url" />
<br />
</div>
Empty values result in form.$error containing the following:
{ "required": [
{
"inumber": {}
},
{
"inumber": {}
}
] }
On the other hand, if validation errors occur outside this ng-repeat:
<input ng-model="name" name="iname" required="true" />
The form.$error object contains the following:
{ "required": [ {} ] }
yet, I'd expect the following:
{ "required": [ {'iname': {} } ] }
Any ideas on why the name of the element is missing?
A running plunkr can be found here:
http://plnkr.co/x6wQMp
As #c0bra pointed out in the comments the form.$error object is populated, it just doesn't like being dumped out as JSON.
Looping through form.$errors and it's nested objects will get the desired result however.
<ul>
<li ng-repeat="(key, errors) in form.$error track by $index"> <strong>{{ key }}</strong> errors
<ul>
<li ng-repeat="e in errors">{{ e.$name }} has an error: <strong>{{ key }}</strong>.</li>
</ul>
</li>
</ul>
All the credit goes to c0bra on this.
Another option is to use one of the solutions from this question to assign unique names to the dynamically created inputs.
I made a function that you pass the form to. If there are form errors it will display them in the console. It shows the objects so you can take a look. I put this in my save function.
function formErrors(form){
var errors = [];
for(var key in form.$error){
errors.push(key + "=" + form.$error);
}
if(errors.length > 0){
console.log("Form Has Errors");
console.log(form.$error);
}
};
Brett DeWoody's answer is correct. I wanted to do the logic in my controller though. So I wrote the below, which is based off of the answer user5045936 gave. This may also help some of you who want to go the controller route. By the way Im using the toaster directive to show my users validation messages.
if (!vm.myForm.$valid) {
var errors = [];
for (var key in vm.myForm.$error) {
for (var index = 0; index < vm.myForm.$error[key].length; index++) {
errors.push(vm.myForm.$error[key][index].$name + ' is required.');
}
}
toaster.pop('warning', 'Information Missing', 'The ' + errors[0]);
return;
}
If you have nested forms then you will find this helpful:
function touchErrorFields(form) {
angular.forEach(form.$error, function (field) {
angular.forEach(field, function(errorField) {
if (!errorField.hasOwnProperty('$setTouched')) {
touchErrorFields(errorField);
} else {
errorField.$setTouched();
}
})
});
}

Backbone - trying to make a filter on a collection with nested object

i'm trying to filter a collection which has models with some nested object. Unfortunately, my result are always empty.
So my models returned in the collection are build like this:
My goal is simple:
I have a view with a list of tag and a content view with all the questions. When a user click on tag, for example, "c#", i want to filter my collection to just return questions with tag "c#"
Before i was doing a fetch on my server and it was working fine, but it was not optimize.
I already have a collection with all the questions so why make a new call, a filter is a better solution i think.
But i didn't succeded with my filter and i don't know if it's possible to do. For now i put my filter in my router because it's more easy to test.
i can't make a filter like this because i have an array of object
getQuestionsByTags: function(query) {
var test = this.questionsCollection.filter(function(model) {
return model.attributes.tags.name == query;
})
console.log('result');
console.log(test);
},
So i was thinking to make a loop but my result is always an empty array.
getQuestionsByTags: function(query) {
var test = this.questionsCollection.filter(function(model) {
_.each(model.attributes.tags, function(tag) {
return tag.name == query;
})
})
console.log('result');
console.log(test);
},
It's maybe simple, but i don't know what to do.
Thanks in advance :)
i've just found a solution that work.
getQuestionsByTags: function(query) {
var flag;
var test2 = this.questionsCollection.filter(function(model) {
flag = false;
_.each(model.attributes.tags, function(tag) {
if(tag.name == query) {
flag = true;
}
})
if(flag) {
return model.attributes;
}
})
console.log('result');
console.log(test2);
},
i put a flag. If he turn true inside the loop, the model has this tag so i return it.
I think it's not very conventional, so if someone have another solution, feel free to post it :)

Resources