I have an <input ng-model='list' ng-list>, and I want to make sure that no duplicates appear in this text field—I want to automatically remove them if the list contains duplicates.
I put a $scope.$watch('list', function(listValues) { in the controller, and try to remove any duplicates from listValues, but have problems. From within the watch function, if I set listValues = _.unique(listValues), $scope.list's value never changes. If I try $scope.list = _.unique(listValues), I get an error about the digest cycle already running.
How can I watch for a scope variable to change, and when it does, perform an operation to change that new value?
Here's an example of it not working: http://plnkr.co/edit/b0bAuP1aXPg3HryxCD9k?p=preview
I thought this would be simple. Is there some other approach that I should be using?
ng-change is probably a better approach in this case. In particular, this attribute of ng-change:
if the model is changed programmatically and not by a change to the
input value
If you place your de-dupe in a function and then use ng-change to call it, I think you will get the results you are after.
Related
I have some inputs, and I want to get data in order of insertion, for example: if I insert the value bbb then the value aa I want to get bbb befor aa
I search in the net and find that this order is ensured using Mapbut I don't know how to use it with ng-model.
thank you in advance.
EDIT
I'm using an object that store the value of the inputs and a customized key passed with value
here is an example, if you insert the values in input 3 then 2 then 1, and click ok, in the console the output will be ordered in an alphabetic order
As stated by #czosel, javascript objects are not ordered, and are usually sorted by alphabetical order of the keys. Therefore, your best solution is probably going to involve going beyond using the ng-model directive as is.
Here are two possibilities you could try out:
Solution 1
In every <input /> place an ng-blur directive that will determine the input's order. For instance:
HTML
<input ng-blur="onBlur('model1')" ng-model="model1" />
<input ng-blur="onBlur('model2')" ng-model="model2" />
controller.js
app.module('myModule').controller('myCtrl', ['$scope', function($scope) {
$scope.count = 0;
$scope.onBlur = function(key){
// check if anything was entered
if($scope[key]){
// make sure this is first time data was entered into this input
if(!$scope[key].order)
$scope[key].order = $scope.count++;
}
};
}]);
Solution 2
Store the values in an array. Similar to the first solution, but instead of keeping count, you would forego the ng-model altogether and manually add the value to an array (after checking that it doesn't already exist, which gets a little tricky with an array). Of course you also have to handle updates yourself, so the first method is definitely going to be simpler. The lodash library will probably be of much help if for some reason you decide to choose this approach.
Lots of luck!
JavaScript Object properties have no guaranteed order, see this answer.
Try using an array instead.
You can Queue(First in First Out) to get data in the order of insertion. Trigger a function and store the values binded in ng-model into queue.
Ex: ng-model = data // here data will be bbb
var queue = [];
function bind(value){
queue.push(value); // value will be bbb
}
if user enters aa then again bind function needs to be called to push the value inside queue
U can get the values in the order of insertion.
I am trying to bind once an object key/field to the value of an attribute (data-oldField) so that if its value changes via user input (angular x-editable tables) I can grab the html element via data-newField and get the value of data-oldField so that I can rename the field/key in the object. I tried using the native :: expression to bind once, but the value of data-oldField changes when a change to the field name is submitted so that the values of date-oldField and data-newField are equal afterwards which is precisely what I do not want.
I also tried using the angular-once library and adding the directives once once-attr-field='field' as per the api, but I got the same result.
<tr ng-repeat='(field, value) in user.data'>
<td>
<span editable-text='field' e-name='name' e-form='rowform' data-newField='{{ field }}' data-oldField='{{ ::field }}' e-required>
{{ field }}
</span>
</td>
...
</tr>
Edit:
Plunker
I was unable to get the values of the data-oldfield and data-newfield attributes to show on the view, but if you observe the values of the attributes using your brower's dev tools and press the "Rename Field" button, you can see the that the value of data-oldfield changes even though I'm using one time binding. Maybe I'm misunderstanding how the $watchers work for this kind of binding?
From the angular docs it appears that using :: will be updating the object to a new value but will not be updating to view:
An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).
I'm not quite sure I understand the question but one approach would be to
defined a function in your controller that would be called on ng-change that could perform your logic.
Another would be to $watch the model but watching can be expensive
UPDATE
I'm a bit confused what exactly you're trying to accomplish but here is a plunkr that has an ng-change where you can reference the new value and the old value of $scope.user using a copy of the object. You can also use the renameField function however you'd like
I've been using $watchGroup to watch a range of fields and trigger a range of functions depending if a particular field has been changed.
I've set up the following plnkr to demonstrate the unexpected behaviour I've came across.
$scope.$watchGroup(['first', 'second', 'third'], function(newValues, oldValues)
{
var message =
{
first: newValues[0],
second: newValues[1],
third: newValues[2],
firstOld: oldValues[0],
secondOld: oldValues[1],
thirdOld: oldValues[2]
};
if(newValues[0] !== oldValues[0]){
console.log('First changed')
}
if(newValues[1] !== oldValues[1]){
console.log('Second changed')
}
if(newValues[2] !== oldValues[2]){
console.log('Third changed')
}
$scope.messages.push(message);
});
The scenario involves three watched fields and I'd like to trigger a function depending on which field has changed. I've been using the 'newValues' and 'oldValues' to monitor which field has changed.
The problem I've came across is that if I've changed the "Second" field then go and change the "First" or "Third" field, the "Second" function is triggered as its storing the previous 'newValues' and 'oldValues' which makes it look like the "Second" field has changed as demonstrated in this image.
I've highlighted the anomaly in the picture. I'd expect once I started changing the "Third" field, the 'newValues' and 'oldValues' for "Second" to be the same as it isn't the field changing.
I'm aware that I could persist two levels of old values and compare them to get around this however I'd expect it to work as I've described. Any clarification if this is a bug or intended functionality would be appreciated.
The angular documentation for $watchGroup states that watchExpressions is an "Array of expressions that will be individually watched using $watch()". Which makes me think that this isn't intended functionality.
Going by the Angular docs for $watch group and that it internally uses $watch for each individual expression I think what you are seeing is the expected behavior
From the docs for $watchGroup,
* The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching
* those of `watchExpression`
* and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching
* those of `watchExpression`
So the new value always has only the latest value and old values contains the previous value.
Secondly, the $watchGroup internally calls the $watch [And what you see is the same behavior for watch]. $watch updates the last value and current value and then calls the listener function only if the current value is different from last value. So in this case, say when you update 'first' expression after 'second' expression, the listener function is not invoked for the 'second' expression and old value is still 'second value'.
If your listener function is really dependent on the which expression has changed, then you are better off using $watch instead of $watchGroup [IMHO, i don't see a performance difference as the $watch is going to be triggered for all expressions]. But if you want call a common handler and pass all new values irrespective of which expression has changed then you could go for $watchGroup.
All said, it would be still be good if you could post this in angular group and get it confirmed from "horse's mouth" :)
I am trying to do the following quite unsuccessfully so far.
I have an string that is semicolon separated. Say a list of emails, so
'email1#example.com;email2#example.com;email3#example.com'
What I am trying to accomplish is split this string (using split(';')) into an array of strings or array of objects (to aid binding). Each of the items I would like to bind to different input elements. After editing I want to read the concatenated value again to send to my backend.
Problem is that when editing one of the split inputs, the original item value is not update (which makes sense as I am guessing the individual items are copies of parts of the original), but I am wondering if there is a way to do something like that.
Note that I want this to go both ways, so watching the individual inputs and updating the original one manually, would just fire an infinite loop of updates.
I have tried a few different ways, including creating an items property get/set using Object.defineProperty to read and right to the string (set was never fired).
take a look at this plnker
You can construct a temporary array on each field update in order to do the string replacement of the old segment with the new value. In order to tackle the lost focus problem you will have to use the ngReapeat's track by $index. The internal array will not be recreated unless you add the separator to your original string.
Here is the complete solution on Plunker
Your main issue is your ng-model attribute on your repeated input element. I would start with making use of ng-repeat's $index variable to properly bind in ng-model. In your original Plunker 'name' is NOT a scope property you can bind to, so this should be changed to ng-model="names[$index]"
Here is a Plunker to reflect this. I made quite a few changes for clarity and to have a working example.
NOTE: You will find that when editing fields directly bound to a repeater, every change will fire a $digest and your repeated <input> elements will refresh. So the next issue to solve is regaining focus to the element you are editing after this happens. There are many solutions to this, however, this should be answered in a different question.
Although binding to a string primitive is discouraged, you could try ng-list.
<form name="graddiv" ng-controller="Ctrl">
List: <input name="namesInput" ng-list ng-model="vm.names"/>
<ul>
<input ng-repeat="name in vm.names track by $index" ng-model="name" ng-change="updateMe($index, name)"/>
</ul>
You'll need both track by $index and an ng-change handler because of the primitive string binding.
function Ctrl($scope) {
$scope.vm = {}; // objref so we can retain names ref binding
$scope.vm.names = ['Christian', 'Jason Miller', 'Judy Dobry', 'Bijal Shah', 'Duyun Chen', 'Marvin Plettner', 'Sio Cheang', 'Patrick McMahon', 'Chuen Wing Chan'];
$scope.updateMe = function($index, value){
// ng quirk - unfortunately we need to create a new array instance to get the formatters to run
// see http://stackoverflow.com/questions/15590140/ng-list-input-not-updating-when-adding-items-to-array
$scope.vm.names[$index] = value; // unfortunately, this will regenerate the input
$scope.vm.names = angular.copy($scope.vm.names); // create a new array instance to run the ng-list formatters
};
}
Here's your updated plunkr
I have this controller with a value.
App.xcontroller = SC.ArrayController.create({
...some code...
array_values = [],
..more code...
})
Now i have somewhere in a view this valueBinding
valueBinding: 'App.xController.array_values',
When I change values in the array the view does not get updated. but when i do
the following in the controller:
var array_values = this.get('array_values');
... adding / removing values to the array....
if (x_values.contains(x)){
x_values.removeObject(x)
} else {
x_values.pushObject(x);
};
this.set('array_values', array_values.copy());
the binding works, the view gets updated. But ONLY with the copy().
I don't want to make a copy of the array, IMHO this is not efficient. I just want to
let the valueBinding know content has changed..
the x values are just a bunch of integers.
The reason i want this: I want to change the value key of a SegmentedItemView. I want to change the active buttons. But I do not know on forehand how many segmentedviews I have
so I thought i bind the value of every generated segemented view to some common array and change that common array to be able to change the active buttons on all of the segmented views. Since each button represents an item with an unique key it works fine. except that i have to copy the array each time.
set the content property of the xcontroller
Bind to the arrangedObjects property of the xcontroller
You need to use KVO compliant methods on the array to get the bindings to fire. The ArrayController itself has an addObject and removeObject methods. Arrays in SC have been augmented with a pushObject method (among others), which is also KVO compliant. So if you use the KVO methods the view should update.
The reason your view does not update is because you are bound to the array, but the array itself did not change. When you do a copy, the array itself changes, so the bindings fire.
You might also want to try
this.notifyPropertyChange('x_values');
in the controller after you make the changes, but that is less preferable to using the built in KVO functionality.