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

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

Related

dynamically change ng-repeat

I have an object made like this:
var examples = {
'example1': {
ex1: {},
ex2: {},
ex3: {}
},
'example2': {
ex1: {},
ex2: {},
ex3: {}
}
}
There is a button linked to a function which fills the example1 object with values on the first click, and the example2 object on a second click.
First click: example1 get values.
Second click: example2 get values.
Then i loop through the object using ng-repeat, like this:
data-ng-repeat="example in examples.example1.ex1"
data-ng-repeat="example in examples.example1.ex2"
data-ng-repeat="example in examples.example1.ex3"
The problem here is, i need the ng-repeat to somehow change and loop through the example2 object aswell when the button is clicked a second time. So my question is, is there a way to change ng-repeat dynamically?
EDIT: The goal is for the first list of appear after the first click, and then for BOTH lists appear together after the second click.
I tried to make a $scope with the value 2 and put it in the ng-repeat, but that didn't work.
$scope.counter = 2; //in a controller
data-ng-repeat="example in examples.example(counter).ex1"
Use dynamic property name
$scope.prop = 'example1';
$scope.click = function (newProp) {
$scope.prop = newProp;
// do whatever you want here
// change prop to example2 or
// or simply toggle Boolean to change prop text
}
And in html use
ng-repeat="example in examples[prop].ex1"
Would it be a solution to loop through "examples" instead, and then have a ng-repeat inside an ng-repeat?
Something like this:
<div data-ng-repeat="example in examples">
<div data-ng-repeat="ext in example">
{{ext.ex1}} {{ext.ex2}} {{ext.ex3}}
</div>
</div>
Do something like Jannik's answer in the html and this in the JS:
$scope.examples = {};
function example(exes) {
this.exes = exes;
}
$scope.onClick = function(exes) {
$scope.examples.push(new example(exes));
}
The exes is a list of ex1,ex2,etc. You may need a $scope.$apply() at the end of this to update the UI.

How can I watch a filtered collection in Angular.JS?

I'm trying to make an event fire whenever a filtered collection is changed. The filtered list is attached to the non-filtered list in ng-repeat.
<tr ng-repeat="item in $scope.filtered = (vm.list | filter:vm.searchText) | limitTo:vm.limit:vm.begin">
And here's my event I want to fire:
$scope.$watchCollection('filtered', function () {
alert($scope.filtered.length);
}, true);
It fires once when the page first loads, before my ajax call populates vm.list, so the alert says 0, but then it should fire again after vm.list gets populated, and every time a change to vm.searchText causes a change to $scope.filtered, but it's not.
I also tried making the $watchCollection method like this:
$scope.$watchCollection('filtered', function (newList, oldList) {
alert(newList.length);
});
But that had the same result.
I also tried doing as is suggested here, and it ended up like this:
<tr ng-repeat="item in catchData((vm.list | filter:vm.searchText)) | limitTo:vm.limit:vm.begin">
$scope.catchData = function (filteredData) {
alert(filteredData.length);
return filteredData;
}
That seemed like it fixed it at first. It now fired when the API call populated the list, and fired again whenever the searchText caused the filtered list to change. Unfortunately it made it so changing the begin option on the limitTo filter no longer worked. Changing the limit option still worked, but not the begin. Changing the begin does still work with the $watchCollection method.
Does anyone have any ideas?
When you create some variables in view, it added as property to current scope. So, in your case you create $scope.filtered, and this added to current scope.
To get it in watch, you just need use same declaration
$scope.$watchCollection('$scope.filtered', function () {
console.log($scope.$scope.filtered.length)
}
But better not use variable name like $scope, so as not to confuse them with angular variables.
so, you can change it ro simple: filtered
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.$watchCollection('$scope.filtered', function(nval) {
if(!nval) return; //nval - new value for watched variable
console.log('as $scope.filtered in view', $scope.$scope.filtered.length);
}, true);
$scope.$watchCollection('filtered', function(nval) {
if(!nval) return; //nval - new value for watched variable
console.log('as filtered in view', $scope.filtered.length);
}, true);
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<input type="text" data-ng-model="search" />
<h3>as $scope.filtered</h3>
<div ng-repeat="item in $scope.filtered = ([11,12,23]| filter:search)">item_{{item}} from {{$scope.filtered}}</div>
<h3>as filtered</h3>
<div ng-repeat="item in filtered = ([11,12,23]| filter:search)">item_{{item}} from {{filtered}}</div>
</div>
you will want to use a function to return the filtered list and set object equality to true.
$scope.$watch(function () {
return $scope.filtered;
}, function (newList) {
alert(newList.length);
}, true);

AngularJS typeahead otions not up to date

I'm writing a directive wrapper around a typeahead input. This directive listens for changes on a link and get's new data + options for the typeahead.
I can simply simulate this behaviour with a $timeout and demonstrated it in this plnkr.co.
JS
app.controller('sample', function($scope, $timeout) {
$scope.options = ['1800', '1900', '2100'];
// Simulate some latency
$timeout(function () {
$scope.options.push('1850');
}, 4000);
});
HTML
<div>
<input type="text" ng-model="optionValue" typeahead="opt for opt in options | filter:$viewValue">
</div>
If you start typing '18' in the input field it shows 1800 as expected. But when 1850 get's added after an amount of time, the selectable options from typeahead are not being updated.
-- FYI my real live directive looks like this --
$scope.$watch($interpolate(url), function (newUrl) {
$http.get(newUrl).then(function (response) {
$scope.options = response;
});
});
I tried to use typeahead="opt for opt in getData()" but this doesn't work because the interpolated value is not yet up to date. It's always one value behind.
Seems like an issue to post on AngularUI Bootstrap website. Matches are getting selected on every keystroke but they don't get updated if you change the underlying data between keystrokes. I don't see any work-around for this, except maybe triggering the appropriate key event handler on the input manually (when you change the collection).
If someone interested in the solution, here is how I solved it at the moment. I'm not happy with the end result, please provide me some feedback :-).
Plunkr
Check out updated-bootstrap.js, I had to add the following in order to make it work:
A custom attribute that'll be use for the $watchCollection
var customOptions = attrs.typeaheadCustomOptions || '';
In the function where it gets the matches I've added a watch if customOptions is provided:
if (customOptions) {
originalScope.$watchCollection(customOptions, function (matches) {
resetMatches();
updateMatches(matches);
});
}
And that was basically it :-), the updateMatches is just an abstraction of existing code. It's not being used by me and the manual update.
var updateMatches = function(matches) {
for (var i = 0; i < matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
id: getMatchId(i),
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = modelCtrl.$viewValue;
};
Opened issue on github

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.

AngularJS: set target (similar to focus) to any $scope property

I have a layout with multiple elements which are able to gain target. I need to target only one element at the time.
Is it possible to define a function on the $scope that receives an object from the model (for example a line item belonging to an invoice) and tell Angular to add a css class wherever the view of this model is?
If I use the ng-class directive, it would force me to add ng-class to all "targetable" elements in the html and each element should know if it is the current target or not. I don't want to add an isTarget() function to each possible element because it will dirty the model.
Example:
This is the html:
<p>{{document.shipFrom}}</p>
<p>{{document.shipTo}}</p>
<ul>
<li ng-repeat="item in document.items">{{item.description}}</li>
</ul>
And this is the controller:
angular.module('myApp').controller('DocumentCtrl', function($scope){
$scope.document = {
shipFrom: 'Origin',
shipTo: 'Destination',
items: [
{description:'item1'},
{description:'item2'}
]
};
})
Is there a way to define $scope.setTarget($scope.document.items[0]) so that it adds a class "on-target" to the element? Note that all the document properties (the items and the shipFrom/To) can gain target.
Edit: Solved
I found a way to get a model's attribute value in my directive's linking function. If I use the $parse service then I can evaluate the model's property attached to the directive simply by instantiating a getter function:
link: function postLink ($scope, $iElement, $iAttrs) {
var valueGetter = $parse($iAttrs.ngModel);
//Then, I can subscribe the directive to a custom event:
$scope.$on('set.target', function (event, elem) {
$iElement.removeClass('on-target alert-info');
//Now it gets the actual value of the model related to the directive
var value = valueGetter($scope);
//If both the model and the event's value are the same, then focus the element.
if (value == elem) {
$iElement.addClass('on-target alert-info');
$scope.setTarget(valueName, elem);
$scope.$apply();
}
return;
});
}//end link function
When I need something to gain target from the controller, then I just do $scope.$broadcast('set.target', $scope.document.shipFrom)
HTML Part :
<p>{{document.shipFrom}}</p>
<p>{{document.shipTo}}</p>
<ul>
<li ng-repeat="item in document.items" ng-click="setTarget(item.description)" ng-class="{'active' : selectedTarget == item.description}">{{item.description}}</li>
</ul>
Controller:
$scope.document = {
shipFrom: 'Origin',
shipTo: 'Destination',
items: [
{description:'item1'},
{description:'item2'}
]
};
$scope.selectedTarget = '';
$scope.setTarget = function(data) {
$scope.selectedTarget = data;
};
DEMO
If you don't want to add an isTarget() function to each possible item, you could add isTarget method on document.
isTarget: function(item){
return this.target === item;
}
and change the html to
<li ng-repeat="item in document.items" ng-class="{'on-target': document.isTarget(item)}">{{item.description}}</li>

Categories

Resources