3

I'm trying to pass the jqLite function element.html directly as a listener of a watcher:

angular.module('testApp', []).directive('test', function () {
  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      scope.$watch('someVariable', element.html); // <-- Passing the function handle as listener
    }
  };
});

However this does not work for some reason, so as a workaround I wrapped the listener in a function:

angular.module('testApp', []).directive('test', function () {
  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      scope.$watch('someVariable', function (newValue) {
        element.html(newValue);
      });
    }
  };
});

This second example works.

I don't understand why the first example is broken. Any ideas?

EDIT: I forgot to mention, the browser does not give me any errors. It just shows me an empty element.

rko
  • 41
  • 3
  • yeah, please read the $watch documentation https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch the listener would have to be a function not a string or element – SoluableNonagon Nov 12 '14 at 18:14
  • what are you trying to achieve passing element html? – SoluableNonagon Nov 12 '14 at 18:15
  • As far as I understand it `typeof element.html` returns `function`, so this should be fine. I was just trying to omit the wrapping function and got confused why this doesn't work. – rko Nov 12 '14 at 18:19
  • that function returns a string though.. so it won't do anything... see explanation below. – SoluableNonagon Nov 12 '14 at 18:21

2 Answers2

1

Actually, it's because of angular's injector that automatically changes the this property of a function, consider this:

var test = function(string) {
    return {
        html: function(value) {
            console.log(this);
        }
    }
}

$scope.$watch('my_watch_expression', test('string').html);

when you check the value of this, here is what you get:

enter image description here

As you can see, it will throw an error on the jQuery library:

enter image description here

this doesn't have an empty function, therefore, it will throw a silent exception and will not work as expected.

Evandro Silva
  • 1,392
  • 1
  • 14
  • 29
  • 1
    This helped me a lot, thank you! So I just had to fix the `this` and now it's all good: `scope.$watch('my_watch_expression', element.html.bind(element));` – rko Nov 13 '14 at 10:23
-1

In the official docs, https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch

the second parameter in $watch is the listener

"The listener is called only when the value ..."

logically if the listener is "called" it has to be a function... or maybe I'm wrong here.

when you do:

link: function (scope, element, attrs) {
  scope.$watch('someVariable', element.html); // <-- Passing the function handle as listener
}

it looks at the element passed in the link function and tries to access .html attribute. It may be a function but it returns a string... hence it successfully runs, but doesn't do any logging because the equivalent is would be something like:

scope.$watch('someVariable', "<div> some content </div>"); 

Which won't do anything, but doesn't cause any errors.

And if you wrap it in a function as you did, then you can do something with it.

SoluableNonagon
  • 11,541
  • 11
  • 53
  • 98
  • 1
    Wouldn't the equivalent of `scope.$watch('someVariable', "
    some content
    ");` be `scope.$watch('someVariable', element.html());`? In the original code I only pass the function without executing it. Otherwise this code wouldn't work either: `scope.$watch('someVariable', listenerFn); function listenerFn (newValue) { element.html(newValue); }`
    – rko Nov 12 '14 at 18:34
  • so you're asking why a function handle of a function that returns a string doesn't do anything when you pass it as a listener to angular. Where angular is expecting a certain type of function format to be passed in as a listener? – SoluableNonagon Nov 12 '14 at 18:38