How can I manually propagate a value to the directive's parent scope in AngularJS?

772 views Asked by At

My AngularJS typeahead FaveDirective needs to bind a single value to the parent scope, and call an update function when that value changes:

Parent html:

<div class="parent-controller-scope ng-scope">
  <my-fave-picker favorite="parent.favorite" on-change="parent.update()">
</div>

Fave picker directive template:

<input 
    type="text" 
    ng-model="vm.favorite" 
    typeahead-on-select="vm.onChange()"
    ng-init="vm.loadTypeaheadValues()"
    placeholder="Pick your favorite Swift"
    uib-typeahead="name for name in ::vm.TypeaheadValues | filter:$viewValue"
    class="form-control">     

Fave picker directive code:

(function (angular, _) {
    'use strict';

    angular
        .module('favorite')
        .directive('MyFavePicker', function() {
            return {
                restrict: 'E',
                templateUrl: 'fave-picker-template.html',
                scope: {
                    favorite: '=',
                    onChange: '&'
                },
                controllerAs: 'vm',
                bindToController: true,
                controller: 'FavePickerController'
            };
        })
        .controller('FavePickerController', function() {
             // etc.
        });

}(angular, _));

This works almost correctly; when the typeahead input is committed, it calls update() on the parent scope as intended. The problem is that this happens before the latest value of favorite is propagated to the parent scope. In other words, if the typeahead has possible values ["Taylor Swift", "Jonathan Swift"] and I type "Tay" and then hit enter to select the value from the dropdown, then at the time the typeahead-on-select callback is executed, I have the following values:

vm.favorite = "Taylor Swift"    
parent.favorite = "Tay"

The parent.update() function therefore operates with the wrong value of parent.favorite ("Tay" instead of "Taylor Swift").

I can think of some bad ways, but what's the right way to do this so that the change to vm.favorite gets propagated back to the parent scope before calling parent.favorite()?

Note that the following things are not possible in my circumstances:

  • inheriting parent scope instead of using isolate scope
  • passing favorite as an argument to update (on-change="parent.update(favorite)")
  • setting a timeout in dir.onChange() before calling parent.update()
1

There are 1 answers

0
georgeawg On

Avoid using two-way, '=', binding in components to propagate values. Instead use one-way, '<', binding for inputs and expression, '&', binding for outputs:

<my-fave-picker  favorite="parent.favorite"
    on-change="parent.favorite=$value; parent.update($value)">
</my-fave-picker> 
app.directive('MyFavePicker', function() {
    return {
        restrict: 'E',
        templateUrl: 'fave-picker-template.html',
        scope: {
            ̶f̶a̶v̶o̶r̶i̶t̶e̶:̶ ̶'̶=̶'̶,̶ 
            favorite: '<',
            onChange: '&'
        },
        controllerAs: 'vm',
        bindToController: true,
        controller: 'FavePickerController'
    };
})

In the component template:

<input 
    type="text" 
    ng-model="vm.favorite"
    ng-change="vm.onChange({$value: vm.favorite})"
    typeahead-on-select="vm.onChange({$value: vm.favorite})"
    ng-init="vm.loadTypeaheadValues()"
    placeholder="Pick your favorite Swift"
    uib-typeahead="name for name in ::vm.TypeaheadValues | filter:$viewValue"
    class="form-control"
/>     

By applying the value in the expression, '&', binding, the value is propagated immediately. With two-way, '=', binding, the value is propagated after a digest cycle.

For more information, see