AngularJS: Binding once to an attribute - javascript

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

Related

Mat-Checkbox binding model can it be optional or null?

Hi all I am new to angular, which I created a dynamic list of checkboxes. Because there are times which I will get the selected value from API which I need to bind to the checkboxes.
Thus I will have the code like below
<div *ngFor="let b of result?.category">
<mat-checkbox (change)="onChangeCheckbox($event)" [value]="b.RefAccess?.accessId"
[checked]="b.RefAccess?.selected" [(ngModel)]="b.BoRefAccess?.selected"
[ngModelOptions]="{standalone: true}">
{{b.BoRefAccess?.accessDesc}}
</mat-checkbox>
</div>
Error "Empty expressions are not allowed ng , Parser Error: The '?.' operator cannot be used in the assignment at column 25 in [b.BoRefAccess?.selected=$event] in..."
My main problem now is the ngModel binding can not be optional but I need it to be optional because there are times API will give me null list item. So I not sure how to handle it.
I also came across another method which keep track on the onChange event but it will only consist checkboxes that being onChanged, those are selected is not included.
The elvis operator can't be used for assignment. You should declare it in a more explicit way.
Instead of
[(ngModel)]="b.BoRefAccess?.selected"
Use
[ngModel]="b.BoRefAccess?.selected" (ngModelChange)="b.BoRefAccess.selected=$event"
So, I'm thinking maybe you could create another variable to bind with ngModel. In the function onChangeCheckbox you created, you could update the value of b.RefAccess.selected if it's different from null.

$watchGroup unexpected behaviour

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" :)

Modifying a watched value within a $watch

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.

Editing a delimited string using multiple input fields in AngularJS

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

How to properly clean form with invalid input from AngularJS controller?

I have an AngularJS form that contains - among other fields - one of type url. The latter is important as this forces the corresponding input to be a valid URL.
Under certain conditions (for instance, a modal dialog with such a form is to be closed), I want to clear that form programmatically. For that purpose, I implemented method reset that basically clears the corresponding form model by setting $scope.formData = {}. Thus, it sets the form model to a new, blank object.
While that assignment clears all valid fields in the rendered HTML form, it does not clear invalid fields, like an invalid URL. For instance, if the user would provide invalid input ht://t/p as URL, that input would not be removed from the rendered form.
I think this is due to the fact that any invalid URL is not reflected by the model - such an invalid URL just wouldn't "make" it to the model because it does not pass validation in the NgModelController#$parsers array. Thus, in the model - there is no URL at all. Consequently, resetting the form model to {} cannot actually change the model's URL as it has not been set yet.
However, if method reset explicitly sets field $scope.formData.url = "", the invalid URL will be cleared properly (at least, the rendered form won't show it anymore). This is caused by the explicit change of the URL in the model. However, now, model variable formData.url contains the empty string (well, not surprisingly), while by using = {}, all fields would be undefined instead.
While assigning individual fields to "" works as workaround for simple forms, it quickly becomes cumbersome for more complex forms with many fields.
Thus, how could I programmatically reset the form efficiently and effectively - including all invalid input fields as well?
I created a Plunker at http://plnkr.co/c2Yhzs where you can examine and run a complete example showing the above effect.
Specify the type of your button as reset. That will not only call the ngClick function, it will also clear the content of the HTML form.
<button type="reset" ng-click="resetFormData()">Reset</button>
I think this solution is moderately elegant: your plnkr reviewed
The big difference is the initialization of your model object.
I think things gets messed up when a variable becomes undefined, it doesn't get updated anymore.. it should be connected (veeeery) deeply with how validation works (docs link)
Returning undefined in that case makes the model not get updated, i think this is exactly what happens behind the curtain
PS: you can recycle resetImplicitly for all your forms in the webapp :)
After trying several answers without success in similar questions, this worked for me.
In my controller:
$scope.cleanForm = function() {
$scope.myFormName.$rollbackViewValue();
};
Just call with some ng-click or any way you want.
Cheers
The Thing is tag is of type "url" which means
if user will enter specifically a valid url then only it will set values of model
If user will expicitly reset it which means setting model values to "" will again make textbox empty .
It is looking like it is setting the values but actually not ,so when you set its value to "" .Angular will set modal value to ""
Lets take another example : put replace "text" with "email"
<input type="email" ng-model="formData.name" />
<br />URL:
<input type="url" ng-model="formData.url" />
<br />
In above code If you will enter invalid email it will not set the values of email's model.
You probably need to make a copy of the model in its pristine state and set the model to pristine when you reset.
There's a good example here:
http://www.angularjshub.com/examples/forms/formreset/
The url form fields are passed into the model only if they are valid. Thus in case of an invlaid-url entry in the form, the scope variable is not assigned with the model and clearing the forms entry by assigning an empty object to the model will still persist the value at the UI front.
The best alternative to this is to assign the model associated with the form data with a null. A similar answer appears here:
https://stackoverflow.com/a/18874550/5065857
ng-click="formData={};"
just give like this ,
<button ng-click="formData={}">(1) Reset Full Data: formData = {}</button>
Reset your form data directly in ng-click itself.

Categories

Resources