Async behavior of AngularJS

402 views
Skip to first unread message

Christopher Armstrong

unread,
Oct 7, 2013, 6:26:49 PM10/7/13
to ang...@googlegroups.com
Hi there,

I was starting with AngularJS a few months ago going through moments of great success and fast gains to learn the language to get to the point after some months that make me think I know nothing about the framework. I don't seem to pick up the directive and ngModel stuff and the Async behavior. I read $q, defer and resolve but I still can't figure out how this ngModel stuff is working.

Lets consider following code in the linking function of a directive:

$watch(attr.ngModel, function(value) {
    console.log("value is set: " + value);
    scope.oldValue = value;
});

console.log("value is undefined: " + scope.oldValue);

The value inside the callback will of course be set. But scope.oldValue = value is initialized "undefined". So even though I set the scope.oldValue inside the $watch callback method that should have resolved the value, the scope.oldValue is undefined outside of the callback method. I have learnt that AngularJS is initializing the variables to "undefined" until the value is returned. But I don't understand what I can do here to listen to the callback completion to process my value further.

Or do I need to put all code that is depending on the returned value in to the $watch callback?

I would only like to get the initial value of the ngModel attribute assigned to a text field. But I somehow can't figure out how to do that, as attr.ngModel is returning "user.email" instead of the value assigned to it. So I assume $watch is required here to get the value from ngModel.

I have also tried to "require" ngModel and then to get the $viewValue.

require: '?ngModel',
link: function(scope, elem, attr, ngModel) {
    console.log(ngModel.$viewValue);
}

In this case I get a "NaN" back. But when i set console.log(attr) I can see that the values are set later. So here the same issue. The variables are initialized empty and somewhen the value is returning. But I can't figure out how I can listen to this event.

Is there somebody that can help me to understand what I miss out?

I read myself through the website, bought two books but my sequential brain seems to have problems to get Async.

Thanks,
Chris

Majid Burney

unread,
Oct 8, 2013, 4:48:42 PM10/8/13
to ang...@googlegroups.com
Here are a few pointers:

1. I consider it best practice in a directive to always operate on external values asynchronously, using $watch or $observe callbacks. You can't rely on scope properties being defined when you link function executes.

2. The Attributes object provides you access to normalized attribute names, but it does not parse expressions for you. Therefore, if you wanted to get the value that the ngModel attribute resolves to, rather than the literal expression string (that is, the scope variable name), you'd need to $parse(attrs.ngModel). But that's probably not what you want to do.

3. scope.$watch is mostly for watching scope properties, you can't just pass it an arbitrary value like attrs.ngModel. Typically you'll scope.$watch the name of a scope property, e.g.,

// where propName is a reference to a value set on the scope, like scope.propName = "sailboat";
scope.$watch("propName", function () { … stuff });

However you can also specify a function as the value to watch, in which case its return value will be checked on each digest cycle. More on that in a moment…

4. If you $watch a value, and it hasn't been assigned a value by the first time a digest loop occurs, your $watch function will be called with an undefined value. So you need to guard against/deal with that in your callback.

5. When you're writing a directive that interacts with ngModel, the correct way to do it is to inject the ngModelController into your link function, as you have done in your second example. However, instead of looking at $viewValue as you have, you'll more often want to read the $modelValue and update it with $setViewValue(), and again, you'll want to do so asynchronously, as that will also notify you whenever the model value changes.

As for knowing when ngModel's value changes, there are two ways I've seen to do it. One is by overriding ngModelController's $render function. Another way, which has proven more reliable in my experience, is to use scope.$watch coupled with a function, as alluded to above. For example:

require: "ngModel",
link: function link(scope, element, attrs, ngModelCtrl) {
  scope.$watch(function () { return scope.$modelValue; }, function (value) {
    // Do something with updated model value...
  });
}

Hope this helps, sorry I can't be any more in-depth at the moment.

Christopher Armstrong

unread,
Oct 8, 2013, 7:20:49 PM10/8/13
to ang...@googlegroups.com
Thanks Majid for your feedback. Your post contains very valuable
information for me. I'll get my head in to your suggestions.

I have also found out in the meantime that certain functions such as
$timeout will trigger a digest cycle. So if I wrap the scope.oldValue in
to $timeout then it'll do a digest cycle before and will display the
value. Not that I want to use it this way but it helped to understand
that also a push to $parsers seems to trigger a digest cycle so that I
can be sure that scope.oldValue will be resolved by then.

The quick wins are great with Angular but boy that's deep diving in to
code to learn the tricky parts. A few more real world examples would
come in handy. The books usually also stop to dive deep at some point. I
think I'll still end up in some headache. But I'm staying with Angular.
As far as I have evaluated AngularJS is the most mature thing/framework
out there compared to others and once I'm able to write re-usable
widgets using directives I think I'll be really happy.....
hopefully :)


angular.module('eaValidators', [])
.directive('eaValidateEmailUnique', ['$timeout', function($timeout)
{
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elem, attr, ctrl) {
if (!ctrl) return;

// email regex for validation
var regex
= /^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
var url = attr.eaValidateEmailUnique;

scope.$watch(attr.ngModel, function(value) {
if(angular.isDefined(value)) {
// save the current value as the same value can
be saved
scope.oldValue = value;
}
});

$timeout(function() {
console.log("Value inside timeout: " +
scope.oldValue);
}, 0);
return true;
}
};
}]);
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "AngularJS" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/angular/ZcA4eOttQzA/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> angular+u...@googlegroups.com.
> To post to this group, send email to ang...@googlegroups.com.
> Visit this group at http://groups.google.com/group/angular.
> For more options, visit https://groups.google.com/groups/opt_out.


Reply all
Reply to author
Forward
0 new messages