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.

+3


source to share


2 answers


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

imgur

+5


source


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.

0


source







All Articles