the $watch - method isnt changing the scope-variable - javascript

In my controller I have a $scope.var=[]
when i click a button, this array gets filled with content.
so the $scope.var.length is changing from 0 to 1 for example.
When the array gets filled I want to show a div, so it has a
ng-if="condition".
This condition is saved in the controller as well:
$scope.controller = false.
Now I want that condition to be changed, when the array get filled:
$scope.$watch('var.length', function(){$scope.condition = true;});
Now (important!), there is a second option to show the div: a button with
ng-click="condition = !condition"
In the test, the ng-click is working perfectly, and the condition is changing between true and false, but when i fill the var = [] with content, the $watch-method isn´t working.
Thanks for help and tipps :)
//EDIT
html:
<div class="row">
<div ng-click="condition= !condition">
<span class="glyphicon clickable" ng-class="{'glyphicon-down': !condition, 'glyphicon-up': condition}" ></span>
</div>
<div class="slideToggle" ng-if="condition">
Text hier
</div>
</div>

You can just watch var itself instead of var.length
Check this out for an example : http://jsfiddle.net/Lzgts/577/
The code -
$scope.$watch('var', function(newVal, oldVal){
console.log(newVal);
if(newVal.length > 0 ){
$scope.condition = true;
}else{
$scope.condition = false;
}
});
This should do the trick for you.
Alternatively, you can watch var.length but you still need some if/else logic inside to check if the newVal is actually 0 or not. Something like this :
$scope.$watch('var.length', function(newVal, oldVal){
if(newVal === 0){
$scope.condition = false;
} else {
$scope.condition = true;
}
});

Such a code may lead to several problems.
You can inspect my code:
plnkr
$scope.$watch('var.length', function(newE) {
if (newE > 0) {
$scope.condition = true;
}
});
The way your var changing your array, if they are equal you have to reinitialize it. Not a very smart idea but in other way you must ensure that you array filling creates new array and angular could see that.
to give it a possibility to change a scope you have to use $timeout in my case, so be care to not get trapped in $apply loop.

Okay I did it:
change the ng-click="condition = !condition"
to ng-click="reverse()"
and in the controller you define the function:
$scope.reverse= function(){
$scope.condition= !$scope.condition;
};
Then the condition is changing as well, because it´s in the same scope now.

Related

AngularJS - change scope value when its key is true within another scope

I have a ng-repeat like this
<li ng-repeat="car in cars">
<p>{{car.model}}</p>
<span ng-show="car.check">🗸</span>
<span ng-show="!car.check">X</span>
</li>
based on this $scope.cars
$scope.cars=[
{'model':'Ford','check':true},
{'model':'Honda','check':true},
{'model':'Ferrari','check':true}
];
So when check is true, it is displayed a 🗸, and when it is false, a X
I have another $scope.filter (that I use for another purposes, but for the shake of simplicity I will just write its content)
$scope.filter = {
"producers": {
"Ford": true,
"Honda": true,
"Ferrari": false
}
}
What I would like is whenever I change the $scope.filter values, that change would be reflected in the $scope.cars (so in this example, if filter.producers.Ferrari:false, the corresponding element in $scope.carswould automatically change as well to 'check':false
You can check the jsfiddle here Thanks in advance!
Edit: as RipTheJacker says, the question is about how to make a function that updates the cars checked value based on the filter values.
Update
I missed the original requirement to update the value. The easiest way is to watch the filter model from your scope:
$scope.$watch("filter", function(nv,ov){
angular.forEach($scope.cars, function(car){
car.check = nv.producers[car.model]
})
}, true)
I've updated your jsFiddle.
Basically just updated your $scope.checkProducerTrue function to:
$scope.checkProducerTrue = function(c) {
angular.forEach($scope.filter.producers, function(value, key) {
if (key == c.model) c.check = value;
});
};
With Angulars' $scope.$watch you can "listen" changes in variables.
I leave you a very simple example based on your JSFiddle... you should optimize the function but i think the idea is clear.
$scope.$watch('filter.producers',function(s){
$scope.cars[0].check = $scope.filter.producers.Ford;
$scope.cars[1].check = $scope.filter.producers.Honda;
$scope.cars[2].check = $scope.filter.producers.Ferrari;
},true)
Link to JSFiddle here

AngularJS, the variables doesn't change in the view

I've created this simple calculation where the user inserts his levels and by that we get the combat calculation. But the problem is, the view never change, because the variables are somehow static.
Please find jsfiddle for code.
Code link :jsfiddle
There are a few issues with your code.
First using a model for each input element does not tell angular that you wish to capture the change events, in other words that you wish angular get notified when these element value has been changed. That's why you need to use the ng-change directive, which role is to bind the new values to the DOM element.
The second issue is that angular does not know when the model values have been changed, until you do not tell to watch them.
Taking into account the above consideration this is how the refactored code should look like:
var app= angular.module("cmbtCalc", []);
app.controller('CombatCalculatorController', function($scope){
$scope.result = 0;
$scope.ALvl = 1 ;
$scope.SLvl = 1 ;
$scope.MLvl = 1 ;
$scope.RLvl = 1 ;
$scope.HLvl = 10 ;
$scope.DLvl = 1 ;
$scope.PLvl = 1 ;
function RangeMage (real){
return 0.325 * (Math.floor( real/ 2)+real);
};
$scope.RealRange=RangeMage($scope.RLvl);
$scope.RealMage=RangeMage($scope.MLvl);
['SLvl', 'ALvl', 'MLvl', 'RLvl', 'HLvl', 'DLvl', 'PLvl'].forEach(function(val) {
$scope.$watch(val, function(newValue, oldValue) {
$scope.melee = 0.325 * ($scope.ALvl + $scope.SLvl);
$scope.base = 0.25 * ( $scope.DLvl + $scope.HLvl + Math.floor ($scope.PLvl / 2 ));
})
});
$scope.calculatecmbt = function (){
$scope.result = Math.max($scope.melee, $scope.RealRange, $scope.RealMage) + $scope.base;
return $scope.result;
};
});
And inside your html you have to define a model for a ng-change directive:
<h2 ng-model="RealRang" ng-change="calculatecmbt">
{{result}}
{{calculatecmbt() | number}}
</h2>
And here is the working code: https://jsfiddle.net/tvbjscp9/5/
I think you does not invoke the function JS FIDDLE
You may have use function invoke on input changed or else use watchers for invoke functions
$scope.$watch('myVar', function(newValue, oldValue) {// You can invoke your custom functions here
});
I guess you will need to call calculatecmbt on change of value and save the output in scope variable that you bind on UI.
<p>Insert your Prayer Level <input type="number" ng-model="PLvl" ng-change="calculatecmbt()"></p>
{{outputVal}}
Controller code
$scope.outputVal = Math.max($scope.melee, $scope.RealRange, $scope.RealMage) + $scope.base;
return $scope.outputVal;
};
I made it work, wasn't passing the function values. Here's the working code :)asdasdasdas
[Working jsfiddle]( https://jsfiddle.net/tvbjscp9/8/)!

xeditable Grid's drop down values cannot change from the outside dropdown

I'm using xeditable angular directive.I need to set the grid's all the drop down values of Status column according to the value of the outside drop down.
I have set up the JsFiddle here.But it's not working.Could you tell me Why ? Thanks in advance.
Update: When I click the cancel button then it's updated.Very strange :( Could you tell me how to sort out this issue ?
HTML
<span editable-select="bulkPaymentType" e-form="tableform" e-ng-options="s.value as s.text for s in statuses" e-ng-change="setBulkPaymentType($data)">
</span>
js
$scope.setBulkPaymentType = function (data) {
for (var i = $scope.users.length; i--;) {
var user = $scope.users[i];
user.status = data;
};
};
JSFiddle
I have found the solution.Here it is :)
HTML
<span editable-select="bulkPaymentType" e-form="tableform" e-ng-options="s.value as s.text for s in statuses" e-ng-change="setBulkPaymentType($data,tableform)">
</span>
JS
$scope.setBulkPaymentType = function (data,tableform) {
for (var i = 0; i < tableform.$editables.length; i++) {
if (tableform.$editables[i].name === 'user.status') {
tableform.$editables[i].scope.$data = data;
}
}
};
Play with it : JSFiddle
Your setBulkPaymentType method is updating the users in your controller scope.
But the value in the editable input is actually a copy from your controller scope. So when you call setBulkPaymentType, you don't see them change. And when you hit cancel button, the form will display with the value in your controller scope which you updated with setBulkPaymentType, that is the reason. I don't think you can modify the $data within each editable directly since they are using isolated scope. There is no way to get access to $data from outside.

AngularJS: Hide parent based on number of hidden children

I have the following code:
<div ng-hide="items.length == 0">
<li ng-repeat="item in items" ng-hide="item.hide == true">
{{ item.name }} <hide-button />
</li>
</div>
Imagine that I have 10 items. When I click the button, I set the item.hide to true and the item disappear. When I hide all 10 items, I want to hide the main div. What's the best way to achieve this with AngularJS?
An approach might be to use a function like this:
$scope.checkItems = function () {
var total = $scope.items.length;
for (var i = 0; i < $scope.items.length; ++i) {
if ($scope.items[i].hide) total--;
}
return total === 0;
};
and change the ngHide attribute on the outer div:
ng-hide="checkItems()"
jsfiddle: http://jsfiddle.net/u6vkf6bt/
Another approach is to declare a $scope.allHidden variable and watch over the array like this:
$scope.$watch('items', function (newItems) {
var all = true;
for (var i = 0; i < newItems.length; ++i) {
if (newItems[i].hide !== true) all = false;
}
$scope.allHidden = all;
}, true);
This is checking when anything inside the array is changed, and check if all the hide attributes are set to true.
Then set it on the ngHide attribute:
ng-hide="allHidden"
jsfiddle: http://jsfiddle.net/u6vkf6bt/1/
Among the two, I would choose the first approach, because deep watching may cause performance issues:
This therefore means that watching complex objects will have adverse
memory and performance implications.
From here, under $watch(watchExpression, listener, [objectEquality]); when objectEquality is set to true.
Yet another approach would be updating the counter of hidden items whenever a single item is hidden. Of course this would require the hiding code to be placed within your controller, like this:
$scope.allHidden = false;
// if items can have hide = true property set already from the start
var hiddenItems = $scope.items.filter(function (item) {
return item.hide;
}).length;
// or if they are all initially visible, a straightforward approach:
// var hiddenItems = 0;
$scope.hideItem = function (item) {
item.hide = true;
hiddenItems++;
if (hiddenItems === $scope.items.length) {
$scope.allHidden = true;
}
}
and the hiding would need to be done like this:
<li ng-repeat="item in items" ng-hide="item.hide == true">{{ item.name }}
<button ng-click="hideItem(item)">hide</button>
</li>
jsfiddle: https://jsfiddle.net/723kmyou/1/
Pros for this approach
no need to iterate over the full items array when doing the "should main div be hidden?" check -> possibly better performance
Cons for this approach
you need to place the hideItem function inside your controller
A couple of reasons why I didn't like the other solutions:
Time complexity is always O(n). instead of counting how many items are visible/hidden - a number we do not care about at all in this problem - we should just ask if there's at least one visible. better performance.
Not reusable enough.
My preferred solution is to actually look for the first visible item:
http://plnkr.co/edit/FJv0aRyLrngXJWNw7nJC?p=preview
angular.module('MyApp',[]);
angular.module('MyApp').directive('myParent', function(){
return {
restrict: 'A',
link:function(scope, element){
scope.$watch(function(){
return element.find('>:visible:first').length > 0;
}, function( visible ){
if ( visible ){
element.show();
}else{
element.hide();
}
})
}
}
});
<div my-parent>
<div class="hidden">one child</div>
<div class="hidden">another child</div>
</div>
<div my-parent>
<div class="hidden">third child</div>
<div>another child</div>
</div>
You can even expand this by passing a selector to 'my-parent' attribute to be used in the directive. allows to better specify which items to look for.
I like this approach as it has almost no assumptions.
This can be used for any scenario where children might be hidden. not necessarily ng-repeat.
However the watch might be costly as we're using a :visible selector.
another - assuming ng-repeat is involved - is to watch the value on the scope - http://plnkr.co/edit/ZPVm787tWwx9nbJTNg1A?p=preview
app.directive('myParent', function(){
return{
restrict: 'A',
link: function(scope, element,attrs){
scope.$watch(function(){
try{
value = scope.$eval(attrs.myParent);
var i = value.length-1;
for ( ; i >= 0; i-- ){
if ( value[i].hide != false ){
return true;
}
}
}catch(e){
return false
}
return false;
}, function( newValue ){
if ( !!newValue ) { element.show(); } else { element.hide();}
})
}
}});
<ul my-parent="awesomeThings">
<li ng-repeat="thing in awesomeThings">{{thing.value}}</li>
</ul>
The watch here might be less costly, not sure, but it doesn't handle the dom, so there's a good chance for that..
While this is very similar to other solutions, it is implemented as a highly reusable directive, and looks for the first visible item.
In my solutions I also assume jquery is available.
There are many other ways to implement this if required:
AngularJS: element.show() in directive not working
regarding watch performance, not sure which way is better. I am using a function to evaluate the watched value and I am not sure if this is the best way to handle it.

What is the most direct way to determine that the inner element should be displayed in AngularJS?

I have a JSON structure which represents as hierarchical elements.
It looks like the following:
{
"url":"http://docsetups.json",
"partnerId":1,
"fieldDefs":
[
{"roleName":"Make","roleId":1,
"children":[{"roleName":"Invoice Number","roleId":11}]
},
{"roleName":"Model","roleId":2,
"children":[
{"roleName":"Manufacturer","roleId":21},
{"roleName":"EquipmentCode","roleId":22},
{"roleName":"EquipmentSSN","roleId":23}
]
}
]
}
Plunker
I've have created a plunker at: http://plnkr.co/edit/betBR2xLmcmuQR1dznUK?p=preview
I am using ng-repeat to display this in elements as a hierarchy of elements like the following:
When I click on either element the entire structure expands and looks like the following:
The code which renders the DOM is nice and easy and looks like the following:
<div class="headerItem"
ng-class="{focus: hover}"
ng-mouseenter="hover = true"
ng-mouseleave="hover = false"
data-ng-click="vm.onClick(item.roleName)"
data-ng-repeat="item in vm.documentSetups.fieldDefs">{{item.roleName}}
<div class="subItem" ng-show="vm.isVisible"
data-ng-repeat="subItem in item.children">[ ] {{subItem.roleName}}
</div>
</div>
vm.isVisible
The thing to focus on here is the subitem which has the ng-show="vm.isVisible" so that it only displays if that value is true.
Show Only The Subitem of the Clicked Parent
However, I'd like to only display the subitem when its parent item is clicked -- instead of showing all subitems like it does now. Can someone offer a good way to do this? I'm hoping to do it without a directive, because I am interested in whether or not this is possible without a directive or if the code is terribly convoluted in that case.
If you have a solution which includes creating a directive, please keep it as simple as possible. Thanks.
I think you should define a flag for every item which determine if the item is open.
Then you pass the item itself into handler:
data-ng-click="vm.onClick(item)
after that - you simply need to invert isOpen flag:
function onClick(item)
{
item.isOpen = !item.isOpen;
}
The whole view snippet:
<div class="headerItem"
ng-class="{focus: hover}"
ng-mouseenter="hover = true"
ng-mouseleave="hover = false"
data-ng-click="vm.onClick(item)" data-ng-repeat="item in vm.documentSetups.fieldDefs">{{item.roleName}}
<div class="subItem" ng-show="item.isOpen" data-ng-repeat="subItem in item.children">[ ] {{subItem.roleName}}</div>
</div>
The plunker: http://plnkr.co/edit/N8mUZaVfmLpnlW4kxzSr?p=preview
#Oleksii You're answer is very close and it did inspire me to develop the following answer so I appreciate your input and I did upvote you. However, there's a bit more to it than what you gave me.
View Solution at Plunker
I forked the previous plunker and you can see the final solution at:
http://plnkr.co/edit/QvyHlLh83bEyvlNkskYJ?p=preview
No Directive Required
Now I can click either or both element and they expand independently. Here's the sample output:
It took a bit of thinking, but what I did first was create a new type which holds a roleName (consider it unique) and a isVisible boolean. I call that type visibleItem and it looks like this:
var visibleItem = function (roleName){
this.isVisible = false;
this.roleName = roleName;
};
After that I created an array to hold all the visibleItems (1 for each node):
var visibleItems = [];
Now when I load the json I go ahead and create 1 visibleItem object for each node and push it into the visibleItems array.
$http.get('items.json')
.success(function(data, status, header, config) {
vm.documentSetups=data;
for (var x = 0; x < vm.documentSetups.fieldDefs.length; x++)
{
visibleItems.push(new visibleItem(vm.documentSetups.fieldDefs[x].roleName));
}
})
They are "keyed" by their roleName (consider it unique).
Next, I had to write two helper methods (setVisibleItem and getVisibleItem)
function setVisibleItem(roleName)
{
for (var x = 0; x < visibleItems.length;x++)
{
if (visibleItems[x].roleName == roleName)
{
visibleItems[x].isVisible = !visibleItems[x].isVisible;
}
}
}
function getVisibleItem(roleName)
{
for (var x = 0; x < visibleItems.length;x++)
{
if (visibleItems[x].roleName == roleName)
{
return visibleItems[x].isVisible;
}
}
return false;
}
Wire Up The Helper Methods
Finally, I wire up the setVisibleItem to the ng-click of the element and I wire up the getVisibleItem to the ng-show directive.
data-ng-click="vm.onClick(item.roleName)"
data-ng-repeat="item in vm.documentSetups.fieldDefs">{{item.roleName}}
<div class="subItem" ng-show="vm.getVisibleItem(item.roleName)"
data-ng-repeat="subItem in item.children">[ ] {{subItem.roleName}}</div>
</div>
Summary Of How It Works
Basically each of those just iterates through the list and checks to insure if the roleName sent in matches the roleName of the item. If it does it sets or gets the value.
Solved Without a Directive and Not Bad
It's a lot more work than you think it'll be, but I didn't have to implement a directive and the code is still fairly basic.

Categories

Resources