AngularJS - ng-if if there is a specific value in array - angularjs

I would like to trigger ngIf when there is a specific value in an array inside the scope.
So I have this on my scope:
var orders = [{
"order_no": 1,
"color": ["blue", "pink", "red"]
}, {
"order_no": 2,
"color": ["yellow", "blue", "white"]
}];
I would like to have an ngIf that shows one element if, for example, yellow is present in one of the color arrays (given that I would not know the data scope in advance, but knowing the structure in advance).
The only way I have found to do that would be when knowing the structure of the scope in advance, so fx ng-if="orders[1].color[0] === 'yellow'"
Thanks!

You can call the indexOf method of the color array inside your HTML template. This way, you don't need to pollute the $scope with additional methods.
Here's how: ng-if="order.color.indexOf('pink') !== -1"
Note: don't forget that expressions in AngularJS are different. One key factor to remember is that they are forgiving.
Forgiving: In JavaScript, trying to evaluate undefined properties generates ReferenceError or TypeError. In Angular, expression evaluation is forgiving to undefined and null.
This means that something like myCtrl.foo.bar.wa.la will evaluate to undefined which doesn't trigger ngIf. You can read more about it here.

You could do this as a function expression
ng-if="hasYellow()"
$scope.hasYellow = () => $scope.orders.some(
order => order.color.indexOf("yellow") > -1
);
Depending on the size of orders and how often it changes, it may be more efficient to just have a hasYellow property and update that when orders is changed.

Related

Prevent AngularJs content flash on $scope update

When I update my $scope like so in a controller
$scope.item = "Hello";
Then the whole DOM for item seems to be removed and then added again. This seems fine, but if I have a list of items and do
$scope.items = Resource.query();
To update all the items then all of the DOM for items is removed and then re-added, this looks a broken and clumsy - is there anyway around this removing and then adding of the DOM elements when $scope is updated?
This issue is further exasperated if $scope.items and its children are used inside several ng-repeat statements as all of those ng-repeat sections are removed and then re-added.
EDIT
I have read this and feel that this is the issue https://www.binpress.com/tutorial/speeding-up-angular-js-with-simple-optimizations/135
That I have so much "stuff" going on the $digest is just slow. I am working on an example but in the mean time imagine this try of data
{
{
id: 1,
name: "name1",
something: {
id: 10,
name: "something10"
else: {
id: 15,
name: "else15"
}
}
}
}
But there are 20 such objects all with nested objects - this appears to be the issue. That there are so many objects being parsed and bound to the DOM that the $watchers are just taking a long time to go over everything.
EDIT 2
I made this demo, perhaps I am using resource wrong? http://plnkr.co/edit/QOickL0Dyi8jmuvG9mzN
But the items are replaced every 5 seconds, on replace they all disappear and then reappear. This is the issue I am having.
This is a common problem when using ng-repeat. In order to track the objects the directive itself by default adds additional property to every object in the array ($$hashkey). And whenever new object is added or removed from the array the same is happening for it's associating DOM element. When the array is replaced with new array (ex. returned from the server) as you mentioned all the DOM elements representing objects from the previous array are removed and replaced with new DOM elements for the new objects. This is simply because the new objects in the array does not have the $$hashkey property even though they may be semantically the same.
A solution for this common problem came in Angular 1.2 version with the track by clause. You can find more detailed explanation about it in this post.
In your case the objects in the array have an 'id' property which it's a good candidate for tracking them if you can guarantee it's uniqueness. So using ng-repeat like this will improve it's performance.
<div ng-repeat="item in items track by item.id">
<!-- tpl -->
</div>
If you'll take a look at $resource documentation, you'll see that the correct way of manipulation with data - to use callback function
Instead of
$scope.items = Resource.query();
Try
Resource.query(function(data) {
$scope.items = data;
});
Did you try ngCloak directive? https://docs.angularjs.org/api/ng/directive/ngCloak

Track changes in a multi-level object in AngularJS

There is an object. It is dynamic and the data in it are constantly changing, say so.
$scope.object = {
"className": "?????",
"property": [
{
"name": "???"
},
{
"prado": "???"
}
]
}
What needs? And it is necessary for the project $scope.object monitor any change in, at all levels of the object. Maybe there is a method similar to the $watchCollection, only advanced? Or another way to keep track of?
I would be glad of any tip. Thank you in advance!
$watch has third optional parameter:
$watch(watchExpression, listener, [objectEquality]);
According to documentation:
When objectEquality == true, inequality of the watchExpression is
determined according to the angular.equals function. To save the value
of the object for later comparison, the angular.copy function is used.
This therefore means that watching complex objects will have adverse
memory and performance implications.
See https://docs.angularjs.org/api/ng/type/$rootScope.Scope
See also How to deep watch an array in angularjs?
But use it carefully.

Can I isolate scope for a div?

I have a page and a model on my scope $scope.model = { ... };
I bind values on my page with values on the model which is nice. I have an address nested somewhere in that model, for the sake of this example lets say
$scope.model = { visits : [{date : ..., addresses : [{ ... }, ...]},
...], ...};
I need to write all the information of the first visits first address somewhere in a div. I know i can type all the fields like this model.visits[0].addresses[0].Zip etc. but there are like 5 fields in model.visits[0].addresses[0] that i need to output. I figured, there most be an easier way? I considered putting a
<div ng-init="a = model.visits[0].addresses[0]">{{a.Zip}}...</div>
and then just access all properties like that. What i really want though, it not to create a new property on the scope called a unless i can narrow the scope for a to just that one div.
Is that possible somehow?
Clarification: I know i can probably redo my model or move the data up on model itself, but this is just something I've run into multiple times and i would just really want to know if there is a solution to a problem like this.
You could create a tiny directive that creates a new scope on the elements you want:
...
angular.module('yourApp').directive('newScope', function () {
return {scope: true};
});
Then you can use it like this:
<div ng-init="tmp=model.visits[0].addresses[0]" new-scope>{{tmp.Zip}}...</div>
Now, the tmp property will be attached to the new scope created by the newScope directive and will not affect the parent scope.
See, also, this short demo.

Duplicates not allowed error in ng-repeat when list of objects in an array

I am confused why ng-repeat is giving me duplicates error. I can solve it using track by $index but I want to know when this error is thrown by angular.
This is clear
<div ng-repeat="a in [1,1,1,1]">...</div>
As there are duplicates value in above array, it will definitely throw an Dups error.
What about list of objects
<div ng-repeat="a in items">...</div>
JS
$scope.items = [
{"ab":1,"bc":3},
{"ab":1,"bc":3}
]
How does angular treats/compare second one to decide whether there are duplicate values or not?
Thanks.
EDIT
Why I am not getting duplication error?
Fiddle DEMO
See this tutorial http://www.anujgakhar.com/2013/06/15/duplicates-in-a-repeater-are-not-allowed-in-angularjs/ .
In your case, because your two objects have the same key values (ab) you get the error. Adding a track by $index might solve the problem.
EDIT
From the source code.
variable in expression track by tracking_expression` – You can also provide an optional tracking function
* which can be used to associate the objects in the collection with the DOM elements. If no tracking function
* is specified the ng-repeat associates elements by identity in the collection. It is an error to have
* more than one tracking function to resolve to the same key. (This would mean that two distinct objects are
* mapped to the same DOM element, which is not possible.) Filters should be applied to the expression,
* before specifying a tracking expression.
As I understand it, two elements in the repeat resolve to the same tracking id ($$hashkey I believe) you will get the error. You should really check out their source code. It's pretty well commented and annotated.
In angular for each object Means(JSON,object hasOwnProperty), angular maintaining unique ID that $$hashKey used for tracking each object and bind it with DOM element, so as for below case :
$scope.items = [
{"ab" : 1", "bc" : 3},
{"ab" : 1, "bc" : 3}
]
While Angular is not maintenance unique ID for Simple Array
$scope.simpleArrray=[1, 1, 1, 1];
In ng-repeat angularJS maintains Hash for each item Iterate and check for uniqueness for JSON object or Array, if Duplicate ID found in case of simple array it raise an error, for any JSON object its added Unique ID if not found, so you are not getting duplicate error in JSON object
Hope i have clarified your doubts
function AController($scope) {
var a = {"ab" : 1, "bc" :3}
var b = a;
$scope.objsInObj = [
a,
b
]
}
In above case, in ng-repeat when object a in Iteration angular adds $$hashKey to a, when b in iteration it which is pointing to a, which already Has $$hashkey, so angular will not add new $$hashkey and return a's Hashkey so it will be Duplicate, so Angular Raise duplicate Error
Finally I clear concepts myself.
Angular checks weather reference of the object is same or not. So however in my case there are two objects which is same but both are pointing to different reference so its not giving duplicate error. See updated fiddle.
function AController($scope) {
var a = {"ab":1,"bc":3}
var b = a;
$scope.objsInObj = [
a,
b
]
}
DEMO

AngularJS: $watch not being triggered for changes to an array of objects

I have a table and the user can chose to filter rows in the table based on certain columns and certain values for these columns. The object structure to keep track of this filter looks like:
$scope.activeFilterAttributes = [
{
"columnName": "city",
"values": ["LA", "OT", "NY"]
},
{
"columnName": "weather",
"values": ["humid", "sunny"]
}
];
So the objects in the array contain the "columnName" and "values" key. "columnName" signifies the column to consider for the filter while "values" contains the filter values. Basically, the above array will result in rows in the table for which the city column contains "LA", "OT" or "NY" as values and the weather column contains "humid" or "sunny" as the values. Other rows which do not contain these values are not shown.
To help understand this object better, if the user wishes to see only those rows who have "LA" or "NY" in the column for "city", the resultant array will look like:
$scope.activeFilterAttributes = [
{
"columnName": "city",
"values": ["LA", "NY"]
},
{
"columnName": "weather",
"values": []
}
];
The user sets or removes these filters. Whenever this happens, the above array is updated.
This update happens correctly and I have verified it - no problem here.
The problem lies with the $watch(). I have the following code:
$scope.$watch('activeFilterAttributes', function() {
//Code that should update the rows displayed in the table based on the filter
}}
While $scope.activeFilterAttributes is updated properly as and when the user updated the filter in the UI, the $watch is not triggered when this is updated. It is triggered the first time when the application loads but future updates have no effect on this.
I have created a fiddle to demonstrate this: http://jsfiddle.net/nCHQV/
In the fiddle, $scope.info represents the rows of the table, so to speak;
$scope.data represents the filter.
Clicking on the button is equivalent to updating the filter(in the case of the fiddle - the data) and thus updating the rows displayed in the table(in the case of the fiddle - the info). But as can be seen, the info is not updated on clicking the button.
Shouldn't $scope.$watch be triggered when the array of objects changes?
The $watch method takes an optional third parameter called objectEquality that checks that the two objects are equal, rather than just share the same reference. This is not the default behavior because it is a more expensive operation than the reference check. Your watch isn't being triggered because it still refers to the same object.
See the docs for more info.
$scope.$watch('activeFilterAttributes', function() {
// ...
}, true); // <-- objectEquality
Add that parameter and all is well.
Accepted answer is a bit out of date now as with AngularJS 1.1.4 the $WatchCollection function was added for use with arrays and other collections, which is far less expensive than a $Watch with the deep-equality flag set to true.
So this new function is now preferable in most situations.
See this article for more detailed differences between $watch functions

Resources