How do I make Angular sync two fields?
I am learning Angular. As an exercise, I'm converting an existing app that uses jQuery to use Angular.
On my page, I have two entrances. One of them is a combo box; the other is text input.
When the user changes the selection in the dropdown list, the text input is populated with custom selection text, unless the user selects the Custom combo box entry in the combo box. When the user selects Custom, the text input is cleared and focus automatically moves to the text input for the user to enter a custom value.
Whenever the user manually moves the focus to typing text and keys on something, the combobox value is automatically changed to "Custom".
What is the appropriate way to do this in Angular?
I suppose that if I had two identical text inputs, I could just bind them to the same model, but that's different. I guess I have to capture events and manually update the model, but I would like to hear from more experienced people.
source to share
You can wrap some logic internally $watch
in your controller, or perhaps prefer directives like ng-click
and ng-change
.
I think Nikos Paraskevopoulos' answer was good, but it didn't really answer your question about focusing / clearing your inputs.
Here's one way to do it. First the HTML template.
<input type="text"
ng-model="vm.selected.value"
ng-change="vm.inputChanged()"
focus-when-empty>
<select ng-model="vm.selected.option"
ng-change="vm.selectionChanged()"
ng-options="option for option in vm.options">
</select>
It's pretty clear. There is a separate model for the text input
and selection
some options for selection and a ng-change
handler for both. The controller could be something like
app.controller('MainController', function() {
var vm = this;
// your options to select from
vm.options = ['custom','one','two','three'];
// current text input value and dropdown selection
vm.selected = {
value: null,
option: null
};
// handle text input
vm.inputChanged = function() {
var index = vm.options.indexOf(vm.selected.value);
vm.selected.option = index > 0 ? vm.options[index] : vm.options[0];
};
// handle dropdown
vm.selectionChanged = function() {
var index = vm.options.indexOf(vm.selected.option);
vm.selected.value = index > 0 ? vm.selected.option : null;
};
});
Focusing on the text input when "custom" was selected was a little more difficult, so it was handled with a simple directive focus-when-empty
.
app.directive('focusWhenEmpty', function() {
return {
restrict: 'A',
scope: {
ngModel: '='
},
link: function(scope, element) {
// if there is no model, focus on element
scope.$watch('ngModel', function(value) {
if (!value) {
element[0].focus();
}
});
}
};
});
And as an added bonus, if you enter any of the values ββin your options, the selection will then be updated accordingly (so it's no longer "customizable").
Here is the relevant plunker, hope it helps! http://plnkr.co/edit/uJeV5L
source to share
Nice exercise :)
Traditional solution with clock and using change event on input:
app.controller('Ctrl', function($scope) {
var self = this, changeIsInternal = false;
this.selection = null;
this.options = [
'custom',
'alpha',
'beta'
];
this.custom = null;
this.customChanged = function() {
if( this.selection !== 'custom' ) {
this.selection = 'custom';
changeIsInternal = true;
}
};
$scope.$watch(
function() {
return self.selection;
},
function(newval, oldval) {
if( newval !== oldval ) { // needed to skip initialization
if( changeIsInternal ) {
changeIsInternal = false;
}
else {
if( newval !== 'custom' ) {
self.custom = newval;
}
else {
self.custom = '';
}
}
}
}
);
});
And its corresponding HTML:
<div ng-controller="Ctrl as ctrl">
<select ng-model="ctrl.selection" ng-options="o for o in ctrl.options"></select>
<br/>
<input ng-model="ctrl.custom" ng-change="ctrl.customChanged()" focus-when="ctrl.selection === 'custom'" />
<br/>
<pre><code>{{ ctrl | json }}</code></pre>
</div>
Another solution is with custom object properties; this is using NO WATCHES and I find Angular 2 friendly:
app.controller('Ctrl2', (function() {
function Ctrl2() {
this._selection = null;
this.options = [
'custom',
'alpha',
'beta'
];
this._custom = null;
}
Object.defineProperty(Ctrl2.prototype, 'selection', {
get: function() { return this._selection; },
set: function(value) {
if( value !== 'custom' ) {
this._custom = value;
}
else {
this._custom = '';
}
this._selection = value;
}
});
Object.defineProperty(Ctrl2.prototype, 'custom', {
get: function() { return this._custom; },
set: function(value) {
this._selection = 'custom';
this._custom = value;
}
});
return Ctrl2;
})());
HTML is the same, omitting the part ng-change
.
( EDIT: setting focus ... ) In both cases, I suggest the following directive to set focus:
app.directive('focusWhen', function($parse) {
return {
restrict: 'A',
scope: false,
link: function(scope, elem, attrs) {
scope.$watch(
$parse(attrs.focusWhen),
function(newval, oldval) {
if( newval && newval !== oldval ) {
elem[0].focus();
}
}
);
}
};
});
The part is if( newval !== oldval )
designed to prevent theft of focus if the actual value selection
is equal "custom"
.
Here is the updated fiddle: http://jsfiddle.net/6dn55t5w/1/
( EDIT comments ... ) Answer from Mickey Viitala interestingly shows the version ng-change
for listening to changes in the select box. This is probably a better fit for this question (instead of using a clock) as the OP points out "When the user changes the selection in the combo box." Use a clock when the model can also be changed programmatically, as in this case the change event will not be triggered. Use ng-change
when it is guaranteed that only the user interface can change the value.
I still find the version Object.defineProperty
interesting as it can handle both UI and programmatic changes without a clock.
source to share