why $viewValue === $modelValue on select? - angularjs

http://plnkr.co/edit/iVvvdbvI3it7KrYZW6mK
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.selectModel = '1';
$scope.inputModel = new Date();
$scope.testOptions = [
{key: '1', description: 'text 1'},
{key: '2', description: 'text 2'},
{key: '3', description: 'text 3'}
];
});
.
<body ng-controller="MainCtrl">
<form name="inputForm">
<strong>how $viewValue works for input</strong><br />
<input type="date" name="input" ng-model="inputModel"/>
<div>$viewValue: {{inputForm.input.$viewValue}}</div>
<div>$modelValue: {{inputForm.input.$modelValue}}</div>
</form>
<br /><br /><br />
<form name="selectForm">
<strong>how $viewValue works for select</strong><br />
<select name="select" ng-model="selectModel" ng-options="item.key as item.description for item in testOptions"></select>
<div>$viewValue: {{selectForm.select.$viewValue}}</div><!--shows '1', i expected 'text 1'-->
<div>$modelValue: {{selectForm.select.$modelValue}}</div><!--shows '1', as expected-->
</form>
</body>
In the above plunker, you can see different meaning of $viewValue for input vs select.
For input, $viewValue is the string user is seeing, $modelValue is data model.
For select, $viewValue and $modelValue are equal, both being data model.
That does not make any sense to me. Why is that?
To clarify, my question is about select element, specifically: where does $viewValue come from (it seems to always equal $modelValue), and why it isnt actual text displayed in select element?

The nature of $viewValue is determined by the directive. The $render method brings the view value to the screen and if the user can change a value, the directive converts the user's input to the view value. How that happens is completely up to the directive.
The default implementation of $render e.g. is empty. The implementation for input simply sets the value of the element:
element.val(ctrl.$isEmpty(ctrl.$modelValue) ? '' : ctrl.$viewValue);
That's why $viewValue is equal to the value that is being displayed. The implementation for select is more complex because you have a lot of options (pun intended). When you use ng-options the HTML that's generated is like this:
<option value="0" selected="selected" label="text 1">text 1</option>
<option value="1" label="text 2">text 2</option>
<option value="2" label="text 3">text 3</option>
As you can see the actual value is a simple index, barely related to your model. Furthermore the selection tag allows multiple selections. So you have several different possibilities as to what the view value is. I guess it was easiest to let $viewValue be equal to the model value.
FWIW: Imagine a custom directive that uses ng-model but does not directly display a value, like a custom slider. It makes perfectly sense to not use a separate view value but simply use the model value.

$viewValue is the inputElement.value which is provided by the browser.
As the input type is Date angular's built-in ngModel parsers will parse the $viewValue will convert it to date and then assign it to model.
Code snippet from AngularJS
ctrl.$parsers.push(function(value) {
if(ctrl.$isEmpty(value)) {
ctrl.$setValidity(type, true);
return null;
}
if(regexp.test(value)) {
ctrl.$setValidity(type, true);
return parseDate(value);
}
ctrl.$setValidity(type, false);
return undefined;
});

Related

Angularjs: How to get value of input without ng-model

I need to make some inputs by ng-repeat, and in my json file I have in object where is a property called name, like this:
"url":"find_company",
"values":[
{
"name":"company name",
"type":"input_search"
},{
"name":"company_phone",
"type":"input_search"
}
]
I want to make search in DB, in search you can find by any field or by two or more field. Field called the same as property of object. So by ng-keyup I need to send to my function
search(field, value)
two arguments. I want to do something like this
<div ng-repeat="value in param.values">
<input ng-if="value.type == 'input_search'"
ng-keyup="search(value.name, this.text)"
type="text">
How can a send to function text of this input without using ng-model? Where this.text is value of input.
since you are using ng-keyup, you can retrieve input value with $event.target.value.
comment: this is fit for normal event like onclick, but not fit for angular.
refer the below example.
angular.module("app", [])
.controller("myCtrl", function($scope) {
$scope.showValue = function(val) {
alert(val);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div ng-app="app" ng-controller="myCtrl">
<input type="test" ng-keyup="showValue($event.target.value)">
</div>
This is how you do it with ngModel:
<div ng-repeat="value in param.values">
<input ng-if="value.type == 'input_search'" ng-model="value.val" ng-keyup="search(value)" type="text">
And in your controller:
$scope.search = function( item ) {
console.log( item.val ); // Here you have the value using ngModel
console.log( item.name ); // Here you have the "name" property of the element inside the loop
}
As you can see, you CAN use ngModel and by passing the object itself to the function you can access its properties from the function in the controller.
Note that there's that this.text in the view - I don't know what it is exactly so I dropped it from the example to make things clearer, but you can use it in your code of course.
I know the question said without using ng-model. But I suspect you may want this because you want to customize when data-binding occurs. If that's the case, you can use ng-model-options with ng-change:
<input type="text" ng-model="yourModel" ng-model-options="{ updateOn: 'keyup' }" ng-change="search()" />
ng-change fires when the model has been updated, which is after keyup in this case. So the value of yourModel will be up to date when search() executes.

why won't my ng-options set a default value?

I have an angular controller as written below;
controllers.controller('editCodeReviewCtrl', ($scope, $rootScope, $modalInstance, User, reviewRequestId, reviewRequestDetail, reviewRequestValue, reviewRequestTitle) ->
console.log "reviewRequestvalue", reviewRequestValue
$scope.reviewRequest = {}
$scope.reviewRequest.id = reviewRequestId
$scope.reviewRequest.detail = reviewRequestDetail
$scope.reviewRequest.value = '25.0'
$scope.reviewRequest.title = reviewRequestTitle
$scope.values = [
{value: '10.0'},
{value: '25.0'},
{value: '50.0'} ]
)
And my view template looks like the below;
<select ng-model="reviewRequest.value" ng-options="ele.value for ele in values" name="value" required class="friendly-margin pull-right">
</select>
I would expect that my drop down would have default value set, in the example case here it should be set to '25.0' but I'm not observing that behavior in the browser.
notice the html that is being output
<select ng-model="reviewRequest.value" ng-options="ele.value for ele in values" name="value" required="" class="friendly-margin pull-right ng-pristine ng-valid ng-valid-required ng-touched">
<option value="?" selected="selected" label=""></option>
<option value="0" label="10.0">10.0</option>
<option value="1" label="25.0">25.0</option>
<option value="2" label="50.0">50.0</option>
</select>
What direction to take this debugging?
i've looked at this post as well, how to use ng-option to set default value of select element which appears consistent with how I've written my own code.
As the documentation explains, when you use an expression such as
label for value in array
what is displayed in the select box is label, and what is bound to the ngModel is value. So, in your expression:
ng-model="reviewRequest.value" ng-options="ele.value for ele in values"
ele is one of the objects in the array of values. And if you want the object {value: '25.0'} to be the one selected in the select box, your ngModel should be a reference to this object (not an equal copy!):
So your controller should set reviewRequest.value to $scope.values[1].
Or, if you want the ngModel to contain the string 25.0and not the object containing this value, the expression should be
ng-options="ele.value as ele.value for ele in values"
In controller $scope.reviewRequest.value = $scope.values[1];

Angularjs ng-selected populate

I am trying to populate the currently selected item in angular js.
The currently selected item is that with the id in event.email.chases[0].id
and I am trying to match it to a select box populated from case.activeChases
This does what I want so far, which updates whenever the select box changes.
<select
class="form-control"
name="chase"
id="chase"
ng-model="customer.chase"
ng-change="addEmailToChase(customer.chase.id, event.email.id)"
ng-options="cor as cor.emails[0].subject for cor in case.activeChases">
<option value="">None</option>
</select>
I need to find the chase in activeChases with activeChases[x].id = event.email.chases[0].id, so the correct value is selected upon the page load.
Could I possibly do this all within a ng-selected attribute?
Something wrong with your model and your ng-options, the object should be the same if you want an "auto-selected"
without a jsfiddle it's hard to reproduce your situation but I tryed something like that
var app = angular.module('App', []);
app.controller('Ctrl', function($scope) {
$scope.case = {
activeChases: [{
emails: [{
subject: 'toto'
}]
}, {
emails: [{
subject: 'tata'
}]
}]
};
// set "default value"
$scope.customer = {
chase: $scope.case.activeChases[1]
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section ng-app="App" ng-controller="Ctrl">
<select class="form-control" name="chase" id="chase" ng-model="customer.chase" ng-change="addEmailToChase(customer.chase.id, event.email.id)" ng-options="cor as cor.emails[0].subject for cor in case.activeChases">
<option value="">None</option>
</select>
</section>
So this was my answer:
<select
class="form-control"
name="chase"
id="chase"
ng-model="event.email.chases[0].id"
ng-change="addEmailToChase(event.email.chases[0].id, event.email.id)"
ng-options="cor.id[0] as cor.emails[0].subject for cor in case.activeChases">
</select>
I wanted the default value to be event.email.chases[0].id in the select, so I just set ng-model to that, and changed the first option to cor.id[0] to use id in index.
I chose a random name for ng-model which was customer.chase which made no sense. event represents an ajax loaded form element so its safe for me to do this in my scope.
Also worth noting that the id in cor.id[0] is actually in a size one array due to incorrect design in the app.

Retrieve all ngModels within a transcluded directive

i'm trying to retrieve all ngModels within a transcluded directive. Is this the right way or is there a simpler solution to find the child model values?
Is there also a selector where i can use queries like this one ("input", "textarea", "select", ...)
The sample: http://plnkr.co/edit/tjjBEa1I1fIISvGbRz7e?p=preview
I don't know if this is the right approach. All your models are inside your $scope so why don't getting it from there directly?
For changes you shouldn't use jQuery like element.on('change', ...) style, instead bind an event listener to the model with $scope.$watch('model', ...) that would be the angular way.
Are you trying to disable all the inputs and clear their values when the 'Disable' checkbox is ticked?
I would recommend adding the ng-disabled directive to your inputs and binding it to a property on your model.
You can easily clear the input values by moving them onto an object on your model and then clearing that property when the controls are disabled.
Updated version of your plunkr: http://plnkr.co/edit/xKRF3rfAB8EcSKEBEeKd?p=preview
Here is the updated code based on your example:
app.js:
app.controller('MainCtrl', function($scope) {
// 1. Bind the 'Disable' checkbox's ng-model to this value.
$scope.disabled = false;
// 2. Move all your model data down one level onto the 'viewData' object.
// Now we can change all the input values just by changing the `$scope.viewData` object.
$scope.viewData = {
user: {
lastname: 'Doe',
firstname: 'John'
},
checker: true,
opt: 'Item 2'
};
// 3. Add a change callback on the 'Disable' checkbox to call this function.
// Replace the $scope.viewData to change the input values.
var originalData = null;
$scope.disabledChanged = function() {
if ($scope.disabled) {
// Clear the previous object.
originalData = $scope.viewData;
$scope.viewData = null;
} else {
// Revert back to the previous object.
$scope.viewData = originalData;
}
}
})
index.html:
<fieldset id="f">
<legend>
<label><input type="checkbox" child-disable child-disable-root="f" ng-model="disabled" ng-change="disabledChanged()" /> Disable</label>
</legend>
<hr />
<p><input type="checkbox" ng-model="viewData.checker" ng-disabled="disabled" /> Test</p>
<p><input type="text" ng-model="viewData.user.firstname" ng-disabled="disabled" />
<input type="text" ng-model="viewData.user.lastname" ng-disabled="disabled" /></p>
<p><textarea ng-model="viewData.multi" ng-disabled="disabled"></textarea></p>
<div>
<select ng-model="viewData.opt" ng-disabled="disabled">
<option>Item 1</option>
<option>Item 2</option>
<option>Item 3</option>
</select>
</div>
<div>
<button>Click</button>
</div>
</fieldset>

Don't record invalid values with ng-model

I really like how the ng-model attribute binds directly to my model and users get instant feedback on their changes. For my use case that's perfect. However, I don't want invalid values to be put into the model where they can throw a wrench into the calculations. I somehow want the model to only be updated if the value in the form control is valid. For invalid values, it's fine for the control value to change while the model value stays fixed.
If I change the source of angular (1.2rc) NgModelController's $setViewValue implementation:
this.$setViewValue = function(value) {
...
if (this.$modelValue !== value) {
this.$modelValue = value;
...
}
};
To this:
this.$setViewValue = function(value) {
...
if (this.$modelValue !== value && this.$valid) {
this.$modelValue = value;
...
}
};
It seems to do exactly what I want, however I don't know how to do this in a proper way. What's the right way to change this behavior? Or are my attempts doomed to failure for some reason?
Update: Added example.
For example look at http://jsfiddle.net/FJvgK/1/ HTML:
<div ng-controller="MyCtrl">
{{validNumber}}
<form>
<input
type="number"
ng-model="validNumber"
required
min="10"
max="20"
/>
</form>
</div>
And the JS:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.validNumber = 15;
}
The number shows properly for values between 10 and 20, but I want it so that if you suddenly type '8' into the box, or delete the second digit leaving '1' the last valid number still shows above. That is, the model always has a valid value, even if the control does not.
I believe the default behaviour of AnugularJS validators are not to update the model if the value passed is invalid. If you look at the developer guide and go through Custom Validation these samples also show that the model is not update or is cleared on invalid value provided in the UI
This is default behaviour, but, you can modify this using ngModelOptions directive
<input
type="number"
ng-model="validNumber"
required
min="10"
max="20"
ng-model-options="{ allowInvalid: true }"
/>
Documentation: https://docs.angularjs.org/api/ng/directive/ngModelOptions See the section 'Model updates and validation'
As Chandermani said, it is the default behavior, here is a example that shows it in action :
<form name="myform">
<input type="text" name="myinput" ng-model="myvalue" ng-minlength="4" required>
</form>
Is the input valid ? {{ myform.myinput.$valid }} <br />
Input's value : {{ myvalue }}
{{ myvalue }} doesn't show anything until you write at least 4 characters in the input.
Best Regards.
EDIT
If you need a default value, I guess you could break down your value into 2 values, using a computed value :
var validNumber = 15;
$scope.validNumber = function () {
if ($scope.myform.myNumber.$valid) return $scope.myNumber;
else return validNumber;
};
I set up an example here : http://jsfiddle.net/7vtew/1/

Resources