AngularJS - ngModel cannot be assigned to function - javascript

My question is very common one. I saw many different cases of people with similar question to mine, but I still does not have my answer.
[My goals]
I want to implement a standalone directive to encapsulate div consisting of label(span) and value(input). There are some specifics that worth mentioning here:
My directive aims to preserve the value of the "input" into my page controller score which is an ancestor of the scope my directive works with. Hence with the proper amount of ".$parent" I can access the desired scope for my ngModel assignment.
My directive does not know the name of the placeholder it shall use to preserve the user input as. This placeholder is coming as an HTML property to my directive.
[My approach]
I am using the directive compile phase to manipulate the directive's HTML (mainly adding properties to directive's template HTML). In the pre-link phase I want manipulate the scope that directive is using in order to add the directive's placeholder to my desired scope and assign it some default value if needed. (I guess I can achieve that with ng-init instead of messing with the scopes, but i believe that is irrelevant to my question).
[What I did]
As I explained above I have a function (determinePlaceholderName()) that returns a string which is the exact value I want to assign to ngModel. For example:
$parent.$parent.$parent.placeHolderName
I want to assign that function to input's ng-model attribute assuming that Angular will treath it as an expression, evaluate it and produce what I expected. Here is how I assign the ng-model property:
compile: function(cElem, cAttrs) {
//Some non relevant code here ....
$(cElem[0]).find("div input").attr("ng-model", "determinePlaceholderName()");
}
Basically what I expect is this to be treated like:
$(cElem[0]).find("div input").attr("ng-model", "$parent.$parent.$parent.placeHolderName");
[About determinePlaceholderName()]
This function I define in the link function of my directive:
link: function ($scope) {
$scope.constructValuePlaceholderName = function() {
//Some logic here.
return result;
}
[My Problem]
And finally what my problem is.
Angular result in an error explaining to me that
<input ng-model="myFunc()">
is non-assignable expression as documented.
[My Question]
How can I dinamically set ngModel if function assignment is forbidden by Angular??
Thank you for your time!

I hope this is what you're looking for:
http://plnkr.co/edit/TojgLNx3TUbmszhfs8Ui?p=preview
Using isolated scope, you can pass the name of the model, and the directive itself is not aware of the scope the model actually sits on or its name, but is able to use it as is and give it to ngModel.
The plunker also provides alternatives in case you don't want to use isolate scope or want to use a function.

Related

Angular data binding - ng-book claims you should bind by object attribute. why?

“it’s considered a best-practice in Angular to bind references in the views by an attribute on an object, rather than the raw object itself.”
Excerpt From: Ari Lerner. “ng-book.” (page 66 in the latest revision).
I've not seen this reference anywhere else and wonder if it really makes any difference?
It's to avoid child scoping issues. There are weird things with the scope on angular objects. Basically, there is scope inheritance for objects on scopes, but not for primitives. Here's a problem I've had.
<input ng-model="theModelOnMyScope" />
$scope.theModelOnMyScope = "This is the model";
Looks fine right? But in some cases it won't update on the scope in the controller you're editing because angular may create a childScope for the ng-model in input, depending on how your page is structured. Now, this is the solution to this particular problem.
<input ng-model="anObject.theModelOnMyScope" />
$scope.anObject = {theModelOnMyScope:"This is the model};
And this will work. The reason is it will is because, if the referenced property on the view is a property of an object, Angular will try to find an object on the current scope, and if it can't, it will go UP a level and try to find an object on the scope's parent. So you can make use of scope inheritance with objects. It doesn't do that with primitives, for some reason. So to avoid child scoping issues, it's just best to attach your properties to objects.

How can I bind ngModel through multiple directives with isolate scopes in AngularJs?

I am trying to bind to a property via ngModel in a layer of directives 3 levels deep. This would be fine, except the middle level contains a ng-if which I believe creates a new scope. The binding is lost at this point.
I have created a jsfiddle to explain the situation:
http://jsfiddle.net/5fmck/2/
Note that it works if the ng-if directive is removed, but I am using ng-if instead of ng-show for performance reasons
Does anyone know how I can get the original ngModel to update from the 'inputDirective' template in the fiddle?
Simple :3
Just remember, that child scope is created = use reference to $parent :)
<div ng-if='someCondition'>
<span>In Wrapper</span>
<input-directive ng-model='$parent.ngModel'></input-directive>
</div>
http://jsfiddle.net/5fmck/3/
// upd
As I know you need to use reference to $parent only if ngModel is primitive, not object.
I suspect this is due to the nature of how scopes inherit from each other, and so you should use objects on the scope, and not primitives, and always pass the objects into directives via attributes if it's to be then to be used in another scope. So instead of:
$scope.test = "test";
and
<wrapper-directive ng-model="test" some-condition="true">
use:
$scope.userInput = {
test: "Test"
}
and
<wrapper-directive user-input="userInput" some-condition="true">
Which can be seen at http://jsfiddle.net/4RBaN/1/ (I've also changed all-but-one of the ngModels to another custom attribute, as if you're not using ngModel specific things, like ngModelController, or integration with ngForm, then I think better to KISS).
The reason is that if you have 2 scopes in a parent/child relationship (via prototypical inheritance), such as the one created by ngIf, if you execute (or if Angular executes):
$parentScope.test = 'test';
$childScope.test = 'My new value';
Then $parentScope.test will still equal 'test'. However, if you use objects:
$parentScope.userInput = {test: 'test'}
$childScope.userInput.test = 'My new value';
Then $parentScope.userInput.test will then equal 'My new value'.
The standard quote from (whom I believe was) the original author of Angular, is "If you haven't got a dot in your model, you're doing it wrong". There are other questions on SO about this, such as If you are not using a .(dot) in your AngularJS models you are doing it wrong?
Edit: If you do want to use ng-model, and always pass a primitive to the directive, and not an object, then there is a way that allows this. You'll need to:
Always ensure there is a dot in the model that is passed to ngModel, in every directive. Each directive therefore needs to create a new object on its scope in its controller (or linking function). So the template for your 'middle' directive will be:
<input-directive ng-model='userInput.test'>
And in its controller:
$scope.userInput = {
test: $scope.ngModel
}
Where ngModel is bound to the value specified in the directive's attributes:
"scope" : {
"ngModel" : "="
}
Set up manual watchers in each directive, so that changes to the model from the inner directives get propagated back up the chain:
$scope.$watch('userInput.test', function(newTest) {
$scope.ngModel = newTest;
});
This can be seen at: http://jsfiddle.net/zT9sD/ . I have to say, it feels a bit complicated, so I'm not quite sure I can recommend it. Still, (in my opinion) better than using $parent, as introducing a new scope can quite easily happen as things get more complicated, and then you'll have to use things like $parent.$parent.

Benefits/drawbacks of additional abstraction layer within Angular's $scope

I have slightly modified the example from the following URL (http://docs.angularjs.org/cookbook/helloworld) as follows, placing the name value within an attrs object property:
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/1.2.9/angular.min.js"></script>
<script>
function HelloCntl($scope) {
$scope.attrs = {
name : 'World'
}
}
</script>
</head>
<body>
<div ng-controller="HelloCntl">
Your name: <input type="text" ng-model="attrs.name"/>
<hr/>
Hello {{attrs.name || "World"}}!
</div>
</body>
</html>
One benefit I can see is that the HTML source code can be searched for /attrs\.\w+/ (e.g.) if there is ever a need to easily find all such attributes within the view rather than the controller (e.g. a search for name could collide with form element names). Also within the controller I can only imagine that partitioning attributes necessary for the front end might lend itself to better organization.
Is anybody else using such a level of abstraction. Are there any possible specific further benefits to it's usage? And most importantly, might there be any specific drawbacks to it.
It's recommended that you always use a dot in your ngModels in order to avoid potential issues with prototypal inheritance that are discussed in Angular's Guide to Understanding Scopes:
This issue with primitives can be easily avoided by following the
"best practice" of always have a '.' in your ng-models – watch 3
minutes worth. Misko demonstrates the primitive binding issue with
ng-switch.
Prototypal inheritance and primitives
In javascripts' approach to inheritance reading and writing to a primitive act differently. When reading, if the primitive doesn't exist on the current scope it tries to find it on any parent scope. However, if you write to a primitive that doesn't exist on the current scope it immediately creates one on that scope.
You can see the problem this can cause in this fiddle that has 3 scopes- one parent and two children that are siblings. First type something in the "parent" and you'll see that both children are updated. Then type something different in one of the children. Now. only that child is updated, because the write caused the child to creates it's own copy of the variable. If you now update the parent again, only the other child will track it. And if you type something into the sibling child all three scopes will now have their own copies.
This can obviously cause lots of issues.
Prototypal inheritance and objects
Try the same experiment with this fiddle in which each ngModel uses a property of an object instead of a primitive. Now both reading and writing act consistently.
When you write to a property of an object it acts just like reading does (and the opposite of how writing to a primitive does). If the object you're writing to does not exist on the current scope it looks up it's parent chain trying to find that object. If it finds one with that name then it writes to the property on that found object.
So, while in the primitive example we started with 1 variable and then after writing to the children ended up with 3 copies of the variable- when we use an object we only ever have the one property on the one object.
Since we almost always, perhaps just always, want this consistent behavior the recommendation is to only use objects properties, not primitives in an ngModel or, said more commonly, "always use a dot in your ngModel"
I do this as well. I also put all action functions (button clicks etc) into a $scope.actions object. And since i use socket.io i put those callbacks into a $scope.events object it usually keeps my controllers nice and organized and easily able to find the function i need to if i need to do any editing.
app.controller('Ctrl',['$scope', function ($scope) {
$scope.data = {
//contains data like arrays,strings,numbers etc
};
$scope.actions = {
//contains callback functions for actions like button clicks, select boxes changed etc
};
$scope.events = {
//contains callback functions for socket.io events
}
]);
Then in like my templates I can do like
<input ng-click="actions.doSomething()">
I also do a partial of this for services. I use a private and public data object
app.factory('$sysMsgService',['$rootScope',function($rootScope){
//data that the outside scope does not need to see.
var privateData = {};
var service = {
data:{
//contains the public data the service needs to keep track of
},
//service functions defined after this
};
return service;
}]);

Difficulty w/ Directives with optional scope values and setting with literals in html

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

AngularJS : Difference between the $observe and $watch methods

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

Categories

Resources