Updated with three methods that work, and the original one that does not
I have made an angular js directive, and I am trying to access the ctrl.$modelValue. It does not work in the main flow.
I have three potential solutions, all of which have drawbacks.
Method 1 does not work as I would hope, and I can't find any other property available in the directive directly accessible in this way.
Method 2 works because it waits until the current flow is complete, and then executes in the next moment. This happens to be after the angular js lifecycle is complete, and the controller seems to be hooked up to the model at this point. This does not seem ideal to me, as it is waiting for all execution to finish. If it is possible, I would prefer to run my code as soon as the controller is linked to the model, and not after all the code in current flow completes.
Method 3 works well, accessing the model from the $scope, and determining what the model is from the string representation accessed on the attrs object. The drawback is that this method uses eval in order to get hold of the addressed value - and as we all know, eval is evil.
Method 4 works, but it seems like an overly complex way to access a simple property. I can't believe that there is not a simpler way than the string manipulation, and while loop. I am not confident that the function for accessing properties is 100% robust. At least I might like to change it to use a for loop.
Which method should I use, or is there a 5th method that has no drawbacks?
DEMO: http://jsfiddle.net/billymoon/VE9dX/9/
HTML:
<div ng-app="myApp">
<div ng-controller="inControl">
I like to drink {{drink.type}}<br>
<input my-dir ng-model="drink.type"></input>
</div>
</div>
Javascript:
var app = angular.module('myApp', []);
app.controller('inControl', function($scope) {
$scope.drink = {type:'water'};
});
app.directive('myDir', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, element, attrs, ctrl) {
// Method 1
// logs NaN
console.log({"method-1": ctrl.$modelValue});
// Method 2
// on next tick it is ok
setTimeout(function(){
console.log({"method-2": ctrl.$modelValue});
},0);
// Method 3
// using eval to access model on scope is ok
// eval is necessary in case model is chained
// like `drink.type`
console.log({"method-3": eval("$scope."+attrs.ngModel)});
// Method 4
// using complex loop to access model on scope
// which does same thing as eval method, without eval
var getProperty = function(obj, prop) {
var parts = prop.split('.'),
last = parts.pop(),
l = parts.length,
i = 1,
current = parts[0];
while((obj = obj[current]) && i < l) {
current = parts[i];
i++;
}
if(obj) {
return obj[last];
}
}
console.log({"method-4": getProperty($scope,attrs.ngModel)});
}
};
});
There are quite a few alternatives, some better than others depending on your requirements, e.g. should you be notified if the view value changes, or the model value, or are you happy with the initial value.
Just to know the initial value you can use either of the following:
console.log('$eval ' + $scope.$eval(attrs.ngModel));
console.log('$parse ' + $parse(attrs.ngModel)($scope));
Both $eval and $parse end result is the same, however $eval sits off $scope, where $parse is an Angular service which converts an expression into a function. The returned $parse function can then be invoked and passed a context (usually scope) in order to retrieve the expression's value. In addition, if the expression is assignable the returned $parse function will have an assign property. The assign property is a function that can be used to change the expression's value on the given context. See $parse docs.
If you need to be informed when the model value changes, you could use $watch, however there are better ways when dealing with ngModel. If you need to track changes to the the model value when the change occurs on the model itself, i.e. inside your code, you can use modelCtrl.$formatters:
ctrl.$formatters.push(function(value){
console.log('Formatter ' + value);
});
Note that $formatters are only called when the model value changes from within your code, and NOT when the model changes from user input. You can also use $formatters to alter the model view value, e.g. convert the display text to uppercase without changing the underlying model value.
When you need to be informed of model value changes that occurs from user input, you can use either modelCtrl.$parsers or modelCtrl.$viewChangeListeners. They are called whenever user input changes the underlying model value:
ctrl.$viewChangeListeners.push(function(){
console.log('$viewChangeListener ' + ctrl.$modelValue, arguments);
});
ctrl.$parsers.push(function(value){
console.log('$parsers ' + value, arguments);
return value;
});
$parsers allows you to change the value from user input to model if you need to, where $viewChangeListeners just lets you know when the input value changed.
To sum up, if you only need the initial value, use either $eval or $parse, if you need to know when the value changes you need a combination of $formatters and $parsers/$viewChangeListeners.
The following fiddle shows all these and more options, based on your original fiddle:
http://jsfiddle.net/VE9dX/6/
Instead of using the native eval use the $eval function on the $scope object:
console.log($scope.$eval(attrs.ngModel))
See this fiddle: http://jsfiddle.net/VE9dX/7/
Related
Assuming that there is an angular custom directive which uses an isolated scope for array object with two-way binding:
// scope values
this.scope = {
myArray: '='
};
So, now if I will add objects to the array everything will work like a charm:
myArray.push(item); // array in the parent scope will be updated
However, when I will try to remove object from array via slice method it will not work - array will remain to be the same:
myArray.slice(itemIndex, 1); // ERROR: array will remain to be the same - item will not be deleted
Basically, slice creates a new array object which, as I understand, is prohibited by angular. So, the question is - what is the right way of passing array between scopes via two-way data binding with an ability of adding / removing objects ?
EDIT: many thanks to #ryanyuyu for the correct answer and his patience in explaining. In a nutshell:
the problem has nothing in common with Angular and coupled with vanilla JavaScript API.
read documentation meticulously because sometimes one character really matters ;-)
Reading the documentation for Array.slice:
slice does not alter. It returns a shallow copy of elements from the original array.
Also of note, the first argument is the starting index, and the second argument is the ending index. To actually change your scope variable, you need to assign the return value like so:
myArray = myArray.slice(itemIndex, itemIndex+1);
More likely, you are looking to directly manipulate your array. Use Array.splice (that's splice with a p) to remove elements from the array.
myArray.splice(itemIndex, 1);
This is an alternative solution that takes advantage of one-way bindings. Two-way bindings should be avoided when you can in Angular. They're more expensive than one-way. I'm adding it because it establishes firm control of the variables involved, and it may be informative to those exploring this type of data binding. (requires at least Angular 1.5)
.directive('myDirective', [function(){
return {
scope:{
myArray: '<',
onArrayUpdate: '&'
},
link: function(scope) {
scope.doThings = function(){
scope.onArrayUpdate({index_1: itemIndex, index_2: 1})
}
}
}
}])
.controller('myController', ['scope',function(scope){
scope.myArray = [];
scope.arraySlice = function(index_1, index_2) {
scope.myArray = scope.myArray.slice(index_1, index_2);//or whatever your intent is
}
}])
<my-directive my-array="myArray" on-array-slice="arraySlice(index_1, index_2)"></my-directive>
Try changing this:
myArray.slice(itemIndex, 1);
to this:
myArray = myArray.slice(itemIndex, 1);
It should work.
I'm a seasoned Java developer by now, and I love how Java uses types, signatures, methods, etc. It makes everything understandable, easy to follow and logical.
Now I'm trying to develop something in Javascript. I'm trying to learn Angular JS. However I am finding it so very confusing to understand how this code is developed.
Take a look at the code at this tutorial about ng-repeat. I'll post a sampling of the code;
app.directive('lkRepeat', function(){
return {
transclude : 'element',
compile : function(element, attr, linker){
return function($scope, $element, $attr){
var myLoop = $attr.lkRepeat,
match = myLoop.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
indexString = match[1],
collectionString = match[2],
parent = $element.parent(),
elements = [];
// $watchCollection is called everytime the collection is modified
$scope.$watchCollection(collectionString, function(collection){
var i, block, childScope;
// check if elements have already been rendered
if(elements.length > 0){
// if so remove them from DOM, and destroy their scope
for (i = 0; i < elements.length; i++) {
elements[i].el.remove();
elements[i].scope.$destroy();
};
elements = [];
}
My question is: How can you read such code? When functions are called, where are the method signatures? Also, when returning a value of function with parameters like $scope, $element, $attr, are these just place holders for the variables that will be passed? If so, then why do they have such funny names, why not drop the dollar sign?
When functions are called, where are the method signatures?
The closet JavaScript comes to having method signatures is that, when declaring a function, you can name the arguments.
function foo (named_argument, another_named_argument) {
Arguments are always optional. If you don't specify a value, it will get undefined.
You can specify as many arguments as you like and the function can access them through the arguments object.
Also, when returning a value of function with parameters like $scope, $element, $attr, are these just place holders for the variables that will be passed?
Yes
If so, then why do they have such funny names, why not drop the dollar sign?
The dollar sign is just a character that is allowed in a JavaScript identifier. Some people use it to indicate that the variable is expected to hold a jQuery object (because jQuery keys all its functionality off a variable called $).
New to angular. Trying to understand the proper way to do things
I am trying to create a directive that will run custom logic when a user clicks on a link before moving to the next page. However, before I can even get to the custom logic I'm having difficulty setting up the directive. I want to be able to:
A) Have certain attributes on the directive be optional (set to different defaults in certain cases)
B) Have a user be able to pass a string literal OR a variable from the controller's scope into the directive's attributes to be used on the directive's scope.
My Problems:
1.) If I use string literals in the directive's attributes, the corresponding $scope property is undefined. I have to access it through the $attrs. This isn't the worst thing but seems like a wrong practice to have to check the $scope.Prop and if it is undefined check $attrs.Prop.
2.) Also, this answer seems to say I need to use single quote when using string literals in the attributes but that behavior does not work in my example.
3.) When I have certain attributes on the directive not set, the default value I set on the $scope in the 'controller' function on the directive is not reflected when rendered. I cannot figure out why.
Code in Plnkr
Any help would be appreciated!
Here is a version where all three cases work:
The literals work without anything special
The scope values work using the usual {{ expression }} syntax
Defaults are provided in the directive's template using this syntax href='{{strHref || 'default href'}}' which I found in this thread.
Here is the full directive definition:
return {
restrict: 'E',
replace: true,
scope: {
strHref: '#link',
strText: '#displayText',
},
template: '<div><div>variables: {{strHref || \'default href\'}}, {{strText || \'default text\'}}</div> result: {{strText || \'default text\'}}<span ng-show="blnIsBackButton"> (Return)</span></div>',
};
Link : http://plnkr.co/edit/RB6shHI0xYa1FkP4eXDy
Scope & can be used for literals /expression. Scope = for setting up 2 way binding and scope = for method call.
You can set default value for property in controller if you want
I think directive name has to be lower case. Can someone confirm this? If i use pageLink it does not work but works for pagelink. (note 'L'). Same for property.
There are some nice videos about scope in http://www.egghead.io/.
I created this in Plunker which uses all 3 scope isolation types
I know that both Watchers and Observers are computed as soon as something in $scope changes in AngularJS. But couldn't understand what exactly is the difference between the two.
My initial understanding is that Observers are computed for angular expressions which are conditions on the HTML side where as Watchers executed when $scope.$watch() function is executed. Am I thinking properly?
$observe() is a method on the Attributes object, and as such, it can only be used to observe/watch the value change of a DOM attribute. It is only used/called inside directives. Use $observe when you need to observe/watch a DOM attribute that contains interpolation (i.e., {{}}'s).
E.g., attr1="Name: {{name}}", then in a directive: attrs.$observe('attr1', ...).
(If you try scope.$watch(attrs.attr1, ...) it won't work because of the {{}}s -- you'll get undefined.) Use $watch for everything else.
$watch() is more complicated. It can observe/watch an "expression", where the expression can be either a function or a string. If the expression is a string, it is $parse'd (i.e., evaluated as an Angular expression) into a function. (It is this function that is called every digest cycle.) The string expression can not contain {{}}'s. $watch is a method on the Scope object, so it can be used/called wherever you have access to a scope object, hence in
a controller -- any controller -- one created via ng-view, ng-controller, or a directive controller
a linking function in a directive, since this has access to a scope as well
Because strings are evaluated as Angular expressions, $watch is often used when you want to observe/watch a model/scope property. E.g., attr1="myModel.some_prop", then in a controller or link function: scope.$watch('myModel.some_prop', ...) or scope.$watch(attrs.attr1, ...) (or scope.$watch(attrs['attr1'], ...)).
(If you try attrs.$observe('attr1') you'll get the string myModel.some_prop, which is probably not what you want.)
As discussed in comments on #PrimosK's answer, all $observes and $watches are checked every digest cycle.
Directives with isolate scopes are more complicated. If the '#' syntax is used, you can $observe or $watch a DOM attribute that contains interpolation (i.e., {{}}'s). (The reason it works with $watch is because the '#' syntax does the interpolation for us, hence $watch sees a string without {{}}'s.) To make it easier to remember which to use when, I suggest using $observe for this case also.
To help test all of this, I wrote a Plunker that defines two directives. One (d1) does not create a new scope, the other (d2) creates an isolate scope. Each directive has the same six attributes. Each attribute is both $observe'd and $watch'ed.
<div d1 attr1="{{prop1}}-test" attr2="prop2" attr3="33" attr4="'a_string'"
attr5="a_string" attr6="{{1+aNumber}}"></div>
Look at the console log to see the differences between $observe and $watch in the linking function. Then click the link and see which $observes and $watches are triggered by the property changes made by the click handler.
Notice that when the link function runs, any attributes that contain {{}}'s are not evaluated yet (so if you try to examine the attributes, you'll get undefined). The only way to see the interpolated values is to use $observe (or $watch if using an isolate scope with '#'). Therefore, getting the values of these attributes is an asynchronous operation. (And this is why we need the $observe and $watch functions.)
Sometimes you don't need $observe or $watch. E.g., if your attribute contains a number or a boolean (not a string), just evaluate it once: attr1="22", then in, say, your linking function: var count = scope.$eval(attrs.attr1). If it is just a constant string – attr1="my string" – then just use attrs.attr1 in your directive (no need for $eval()).
See also Vojta's google group post about $watch expressions.
If I understand your question right you are asking what is difference if you register listener callback with $watch or if you do it with $observe.
Callback registerd with $watch is fired when $digest is executed.
Callback registered with $observe are called when value changes of attributes that contain interpolation (e.g. attr="{{notJetInterpolated}}").
Inside directive you can use both of them on very similar way:
attrs.$observe('attrYouWatch', function() {
// body
});
or
scope.$watch(attrs['attrYouWatch'], function() {
// body
});
I think this is pretty obvious :
$observe is used in linking function of directives.
$watch is used on scope to watch any changing in its values.
Keep in mind : both the function has two arguments,
$observe/$watch(value : string, callback : function);
value : is always a string reference to the watched element (the name of a scope's variable or the name of the directive's attribute to be watched)
callback : the function to be executed of the form function (oldValue, newValue)
I have made a plunker, so you can actually get a grasp on both their utilization. I have used the Chameleon analogy as to make it easier to picture.
Why is $observe different than $watch?
The watchExpression is evaluated and compared to the previous value each digest() cycle, if there's a change in the watchExpression value, the watch function is called.
$observe is specific to watching for interpolated values. If a directive's attribute value is interpolated, eg dir-attr="{{ scopeVar }}", the observe function will only be called when the interpolated value is set (and therefore when $digest has already determined updates need to be made). Basically there's already a watcher for the interpolation, and the $observe function piggybacks off that.
See $observe & $set in compile.js
In angular, I have an object that will be exposed across my application via a service.
Some of the fields on that object are dynamic, and will be updated as normal by bindings in the controllers that use the service. But some of the fields are computed properties, that depend on the other fields, and need to be dynamically updated.
Here's a simple example (which is working on jsbin here). My service model exposes fields a, b and c where c is calculated from a + B in calcC(). Note, in my real application the calculations are a lot more complex, but the essence is here.
The only way I can think to get this to work, is to bind my service model to the $rootScope, and then use $rootScope.$watch to watch for any of the controllers changing a or b and when they do, recalculating c. But that seems ugly. Is there a better way of doing this?
A second concern is performance. In my full application a and b are big lists of objects, which get aggregated down to c. This means that the $rootScope.$watch functions will be doing a lot of deep array checking, which sounds like it will hurt performance.
I have this all working with an evented approach in BackBone, which cuts down the recalculation as much as possible, but angular doesn't seem to play well with an evented approach. Any thoughts on that would be great too.
Here's the example application.
var myModule = angular.module('myModule', []);
//A service providing a model available to multiple controllers
myModule.factory('aModel', function($rootScope) {
var myModel = {
a: 10,
b: 10,
c: null
};
//compute c from a and b
calcC = function() {
myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10);
};
$rootScope.myModel = myModel;
$rootScope.$watch('myModel.a', calcC);
$rootScope.$watch('myModel.b', calcC);
return myModel;
});
myModule.controller('oneCtrl', function($scope, aModel) {
$scope.aModel = aModel;
});
myModule.controller('twoCtrl', function($scope, aModel) {
$scope.anotherModel = aModel;
});
Although from a high level, I agree with the answer by bmleite ($rootScope exists to be used, and using $watch appears to work for your use case), I want to propose an alternative approach.
Use $rootScope.$broadcast to push changes to a $rootScope.$on listener, which would then recalculate your c value.
This could either be done manually - i.e. when you would be actively changing a or b values, or possibly even on a short timeout to throttle the frequency of the updates. A step further from that would be to create a 'dirty' flag on your service, so that c is only calculated when required.
Obviously such an approach means a lot more involvement in recalculation in your controllers, directives etc - but if you don't want to bind an update to every possible change of a or b, the issue becomes a matter of 'where to draw the line'.
I must admit, the first time I read your question and saw your example I thought to myself "this is just wrong", however, after looking into it again I realized it wasn't so bad as I thought it would be.
Let's face the facts, the $rootScope is there to be used, if you want to share anything application-wide, that's the perfect place to put it. Of course you will need to careful, it's something that's being shared between all the scopes so you don't want to inadvertently change it. But let's face it, that's not the real problem, you already have to be careful when using nested controllers (because child scopes inherit parent scope properties) and non-isolated scope directives. The 'problem' is already there and we shouldn't use it as an excuse not follow this approach.
Using $watch also seems to be a good idea. It's something that the framework already provides you for free and does exactly what you need. So, why reinventing the wheel? The idea is basically the same as an 'change' event approach.
On a performance level, your approach can be in fact 'heavy', however it will always depend on the frequency you update the a and b properties. For example, if you set a or b as the ng-model of an input box (like on your jsbin example), c will be re-calculated every time the user types something... that's clearly over-processing. If you use a soft approach and update a and/or b solely when necessary, then you shouldn't have performance problems. It would be the same as re-calculate c using 'change' events or a setter&getter approach. However, if you really need to re-calculate c on real-time (i.e: while the user is typing) the performance problem will always be there and is not the fact that you are using $rootScope or $watch that will help improve it.
Resuming, in my opinion, your approach is not bad (at all!), just be careful with the $rootScope properties and avoid ´real-time´ processing.
I realize this is a year and a half later, but since I've recently had the same decision to make, I thought I'd offer an alternative answer that "worked for me" without polluting $rootScope with any new values.
It does, however still rely on $rootScope. Rather than broadcasting messages, however, it simply calls $rootScope.$digest.
The basic approach is to provide a single complex model object as a field on your angular service. You can provide more than as you see fit, just follow the same basic approach, and make sure each field hosts a complex object whose reference doesn't change, i.e. don't re-assign the field with a new complex object. Instead, only modify the fields of this model object.
var myModule = angular.module('myModule', []);
//A service providing a model available to multiple controllers
myModule.service('aModel', function($rootScope, $timeout) {
var myModel = {
a: 10,
b: 10,
c: null
};
//compute c from a and b
calcC = function() {
myModel.c = parseInt(myModel.a, 10) * parseInt(myModel.b, 10);
};
calcC();
this.myModel = myModel;
// simulate an asynchronous method that frequently changes the value of myModel. Note that
// not appending false to the end of the $timeout would simply call $digest on $rootScope anyway
// but we want to explicitly not do this for the example, since most asynchronous processes wouldn't
// be operating in the context of a $digest or $apply call.
var delay = 2000; // 2 second delay
var func = function() {
myModel.a = myModel.a + 10;
myModel.b = myModel.b + 5;
calcC();
$rootScope.$digest();
$timeout(func, delay, false);
};
$timeout(func, delay, false);
});
Controllers that wish to depend on the service's model are then free to inject the model into their scope. For example:
$scope.theServiceModel = aModel.myModel;
And bind directly to the fields:
<div>A: {{theServiceModel.a}}</div>
<div>B: {{theServiceModel.b}}</div>
<div>C: {{theServiceModel.c}}</div>
And everything will automatically update whenever the values update within the service.
Note that this will only work if you inject types that inherit from Object (e.g. array, custom objects) directly into the scope. If you inject primitive values like string or number directly into scope (e.g. $scope.a = aModel.myModel.a) you will get a copy put into scope and will thus never receive a new value on update. Typically, best practice is to just inject the whole model object into the scope, as I did in my example.
In general, this is probably not a good idea. It's also (in general) bad practice to expose the model implementation to all of its callers, if for no other reason than refactoring becomes more difficult and onerous. We can easily solve both:
myModule.factory( 'aModel', function () {
var myModel = { a: 10, b: 10 };
return {
get_a: function () { return myModel.a; },
get_b: function () { return myModel.a; },
get_c: function () { return myModel.a + myModel.b; }
};
});
That's the best practice approach. It scales well, only gets called when it's needed, and doesn't pollute $rootScope.
PS: You could also update c when either a or b is set to avoid the recalc in every call to get_c; which is best depends on your implementation details.
From what I can see of your structure, having a and b as getters may not be a good idea but c should be a function...
So I could suggest
myModule.factory( 'aModel', function () {
return {
a: 10,
b: 10,
c: function () { return this.a + this.b; }
};
});
With this approach you cannot ofcourse 2 way bind c to an input variable.. But two way binding c does not make any sense either because if you set the value of c, how would you split up the value between a and b?