0

I am currently refactoring some code in an angular 1.5.8 application to use components.

(Very similar to some steps in this guide: https://teropa.info/blog/2015/10/18/refactoring-angular-apps-to-components.html) The basic cases are working correctly.

But I get a problem when I need to have my component both updates a variable and also call a function (both bound from parent). In this case, the function call seems to happen before the variable is bound. So when the method executes on the parent it is still using the old contents of the variable.

How can I make sure the variable is updated before the method executes?

See comment in the code below, it's the two lines of the reset() function.

angular.module('searchfieldComponent', [])

.component('searchfieldComponent', {
    templateUrl: "/js/common/components/searchfield.component.tpl.html",
    bindings: {
        labelText: '@',
        searchText: '=',
        searchCallback: '&'
    },
    controllerAs: "vm",
    controller: [function() {

        var vm = this;

        vm.search = function() {
            vm.searchCallback();
        }

        vm.reset = function() {
            vm.searchText = null;
            // When the method bound to searchCallback executes in the parent,
            // the variable bound to searchText has not yet been set to null
            // it is still the old value.
            vm.searchCallback();
        }
    }]
});
PMBjornerud
  • 829
  • 1
  • 8
  • 17
  • What happens when you use an object to pass data between components, then modify its property? Like `searchData: '="; /*...*/ vm.searchData.text = null` – raina77ow Feb 15 '17 at 09:47
  • That works. Thanks! I guess this is because it's the same object the whole time so I avoid the timing issues this way. Feel free to post it as answer, but I consider wrapping in objects to be a workaround, not an optimal solution. I'll accept it in a few days if no better answer shows up. – PMBjornerud Feb 15 '17 at 10:03

1 Answers1

1

The key to the problem is thinking about what this line...

vm.searchText = null;

... does. Sure, it updates the value set on 'child' scope. But when exactly is 'parent' value updated?

It's just not possible to directly 'link' parent and child scope variables via standard prototype inheritance mechanism. While you query child.foo property, the name resolution substitutes its value with child.__proto__.foo one. But as soon as you assign something to child.foo, the prototype's property will be shadowed.

AngularJS lets you out of this jail with its trademark digest approach. It's in the digest phase the changes to the child scope propagate to the parent one (check this answer for more details). But if you want to do something based on that immediately, you're in trouble.

What's are the remedies? The one I'd suggested in comments is the most known and tried one: use shared object instead of primitive, like this...

bindings: {
  labelText: '@',
  searchData: '=',
  searchCallback: '&'
}

// then, in vm methods
vm.searchData.searchText = 'anything';

... so that you never change the reference stored in vm.searchData - you augment its properties instead. And as both components 'look' at the same place, obviously they both see the change.


The better idea might be rethinking the structure of searchfieldComponent overall, though. It's not quite clear to me why exactly the parent scope here should know about the value of searchText. See, the whole point of Angular component is turning somewhat loosely-defined directives into entities with strict set of inputs and outputs.

For a generic searchField, it might be a good idea to provide it with some predefined defaultValue (so that will be an input, marked with '>' sign). But when you need to change something, just let component emit the changedValue via an output function (similar to searchCallback, but with a better name) - and pass this value into this function as a parameter.

Community
  • 1
  • 1
raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • Maybe my component is too small? Using a component is not required if I could avoid duplicate code through some other means, Here, only the parent knows the context of the searchText. For the component it is just a string without context. The component provides a tiny bit of common code (layout, enter key detection and a button to clear the text). – PMBjornerud Feb 17 '17 at 09:22
  • Ok, but why does it matter? You still need inputs and outputs for this component to be properly decoupled - to be a component, not just an extension tightly-coupled to the parent. – raina77ow Feb 17 '17 at 12:45