In the following plunk:
http://plnkr.co/edit/ss3HTb?p=info
I am attempting to bind an attribute to an isolate scope. However, it is not working. I'm wondering if anyone knows why that is?
I've looked through several resources, including:
Need some examples of binding attributes in custom AngularJS tags and http://www.sitepoint.com/practical-guide-angularjs-directives-part-two/
You specified the isolate scope as
scope: {
attr: "=attributeName"
}
What that does is take the value of attributeName specified in your html as an expression in your scope and bind that to attr in your directive scope. But what you did was just to specify "hello" without having a hello object in your scope. To get it to work the way you have it now, you have to use the # attribute binding like so:
scope: {
attr: "#attributeName"
}
The other change that you need to do is how you specify the value in the html. You use titleCase when you define the attribute, but in the html instead of titleCase you need to seperate the "words" by a dash and use all lowercase. So attributeName becomes attribute-name
<node attribute-name="hello"></node>
On the other hand, if you actually want to bind to the value of an object in your scope, then make sure that you specify it in your scope and then you can use it in your directive (and preferably use the dot notation).
I have you updated your plunker to show you how they work.
Related
Noticed strange thing, when I name directive scope parameter dataSource it always undefined.
Here is an example: http://plnkr.co/edit/F0wIVUTj9lavVZyFIxKn?p=preview
If you change dataSource to for example ds everything works.
Question: why?
As you can read here, angular normalizes all attributes, a standard prefix for attributes is data-. So when you name your attribute data-source, it will actually be bound to your directive as source
Your corrected fiddle code
Now can be accessed as $scope.source in your directive.
I have a view as follwing,
<li ng-repeat="img in people.images">
<img ng-src="{{img}}" ng-click="setImage(img)">
</li>
Its working. But my doubt here is, the attribute ng-click should have been set the img inside doubly braces to be executed as in ng-src as ng-click="setImage({{img}})". Its shown below,
<li ng-repeat="img in people.images">
<img ng-src="{{img}}" ng-click="setImage({{img}})">
</li>
But the later is not working.
How the expression is parsed here and how does ng-click vary from ng-src?
I really confused here. Any help highly appreciated.
The difference is the following: Some of the directives use expressions, some don't. This is how they "vary" from each other. You may use the angular documentation to see, which directive use expressions and which don't.
Example for ng-click: https://docs.angularjs.org/api/ng/directive/ngClick It uses an expression:
Example for ng-src: https://docs.angularjs.org/api/ng/directive/ngSrc It doesn't use an expression:
ngClick lets you define a JavaScript-like expression, while ngSource lets you define an usual String, where you also may define an expression, inside the double braces. According to the AngularJS documentation, everything inside of the double braces is also an expression: https://docs.angularjs.org/guide/expression.
I think that the "need" to have the difference between "Expression" for "ngClick" and "String" for "ngSrc" come from the original attributes they derive from: onclick and src.
The attribute src is used to reference a resource as String, while using the attribute onclick, you may use javascript. The usual case is to call a function in onclick, i.e. onclick="doSomething();". I assume that angularjs uses these attributes as a base, this is why the workflow is similar. ngClick lets you use usual javascript expressions, while ngSrc lets you use a String and add a {{}} expression if you need to.
The benefit of using ngSrc with a variable, for example ngSrc="http://localhost/{{myRessource}}" is, that it is evaluated only after $scope.myRessource is set, not before. It uses the observer pattern to render the view as soon as the variable is set, as far as I know.
According to your example, the second one which doesn't work:
<li ng-repeat="img in people.images">
<img ng-src="{{img}}" ng-click="setImage({{img}})">
</li>
It doesn't work because of a syntax error. ngClick uses javascript expression, and the syntax of setImage({{img}}) is not correct - you would not use double brackets around variables either in a javascript function.
Further: $interpolate and $parse
Like stated above, some directives, like ngClick use expressions, while other directives like ngSrc use plain Strings combined with {{}}. The difference between the two on angularJs side is the following:
a directive using an expression is evaluated by angularjs using $parse (Read here from heading "Text and attribute bindings")
a directive using a String is evaluated by angularjs using $interpolate (Read here from heading "Context")
Example:
$scope.varOne = "asdasdasd";
var test1 = $interpolate("http://localhost/{{varOne}}");
$scope.displayOne = test1($scope);
At first, we declare a variable varOne. $interpolate returns a function which needs to be called with the scope, we will bind this to the local variable test1. Then we will call test1 using the $scope. As a result, we will have on displayOne the String http://localhost/asdasdasd.
var test2 = $parse("1+5-3");
$scope.displayTwo = test2($scope);
$parse is evaluating javascript expressions, as a test we may use the calculation "1+5-3". After calling test2, the result will be 3.
I prepared a fiddle, so you can see this: http://jsfiddle.net/wSN54/6/
You may also try using brackets in the $parse evaluation (the same that happens in your second example):
$scope.varTwo = 2;
$scope.varThree = 3;
var test2 = $parse("{{varTwo + varThree}}");
$scope.displayTwo = test2($scope);
This must resolve in an error, because the double braces are not used in usual javascript expression:
Check it out fiddle: http://jsfiddle.net/wSN54/8/
As far as I know, if you put img param inside double brackets, when the page is rendered, you will notice that the parameter inside setImage function is a value of img. If you put img without brackets you will have rendered "setImage(img)" and in both case that will work.
Example:
imagine that you have one item in people.images model, and its for example "example.jpg". If you use brackets in setImage function, when the page is rendered you will see the attribute "ng-click=setImage('example.jpg')", in another approach you will have "ng-click=setImage(img)". In first case AngularJS don't need to parse value from your parameter, because you already put 'example.jpg' like a parameter, in another case AngularJS will parse value from img parameter/item before your function is executed.
TLDR; ng-src $interpolate the argument as template where ng-click $parse the expression
Long version
Copied from angular official docs
param=ngSrc, type=template
param=ngRepeat, type=repeat_expression
details refer to https://docs.angularjs.org/api/ng/directive/ngSrc and https://docs.angularjs.org/api/ng/directive/ngRepeat
What is expression - https://docs.angularjs.org/guide/expression
What is template(markup) - https://docs.angularjs.org/api/ng/service/$interpolate
I'm attempting some Angular and I still have my JQuery head on.
I have a div which contains a dropdown nav which contains a link. When the link is clicked I want the div to go away and be replaced by some ajaxed content, and I want flagged to be set on the flaggable scope.
A have created 3 nested directives, each of which has it's own scope (scope:true):
flaggable, for the div, this AJAXes in content when flagged is set to true.
dropdown, for the nav, this folds and unfolds the nav when the folded state is set on it's scope.
flagButton, this sets flagged on the nearest containing flaggable element.
My question, and it's probably a simple one, is how do I get from the flagButton scope to the nearest containing flaggable scope, so that I can set the flagged parameter in the flaggable scope.
I don't want to assume the flagLink is always in a dropdown, or that there are always 3 nested scopes, there may be more.
In JQuery this would be simple, I would just use traversal to get the .closest('.flaggable') element, and set the value on that. This doesn't seem very Angular to me though. I very well may be attempting something ridiculous.
Thanks!
The scopes inherit from each other. There's no need to know which one is the nearest flaggable scope.
E.g. you can have this in your flaggable scope:
$scope.setFlag = function(val) { ... };
In your flaggable scope you can call $scope.setValue(val), because you can always be sure that this function exists.
Another approach would be setting up a controller for flaggable and specify require: '^flaggable' in the definition of your flagButton directive.
Hint: use the controller and the require attributes in your directive directive definition objects (and read the guide).
I have a form controller with 2 object models model1:{name:"foo"} model2:{name:"model2"}
I created 2 directives (both which create isolated scopes) . One with Element only binding which uses model1 and another one with Attribute only binding which uses model2.
The nesting is like so:
<div myattibute="model2">
<mytag my-model="model"></mytag>
</div>
The attribute only directive doesn't have a template and the tag directive has a template.
The problem is that I am getting undefined in mytag directive for the model.
1.Can someone see the problem and explain it in the plnkr ?
http://plnkr.co/edit/Q23XqY?p=preview
Partial Solution:
A working example with adding an empty div template with just ng-transclude for the myattribute directive makes it work. With i mandated that this attribute directive is on a div it that I would have wanted it to be placeable on any div, span etc.
Here is the working example:
http://plnkr.co/edit/z0M5ys?p=preview
2.How is ng-transclude affecting scope inheritance?
3.Can't I create this attribute with only business logic without any markup ?
Isolate scopes are best avoided except for rare cases they add unnecessary complexity. It is much simpler to just use $scope.$watch to bind to expressions in attributes like this:
$scope.$watch(attrs.myModel, function(newValue, oldValue) {})
$scope.$watch(attrs.myattribute, function(newValue, oldValue) {})
This way your directives can either share the parent scope they were declared in and handle binding to it using $watch expressions, or they can create a child scope using { scope: true } if needed.
Here is one possible solution: http://plnkr.co/edit/mm2q67?p=preview
Keep in mind that your myTag directive can use an isolate scope if you'd really like to do it that way, but the myattribute one can't as that will break the scope inheritance chain for myTag
I'm trying to create a custom component that receives arguments in a ng-repeat loop.
So for example, say I have a component named "mycomp" that receives a custom argument "name" in a ng-repeat:
<mycomp name="{obj.name}" ng-repeat="obj in list" />
And in my directive the isolated scope is defined like this:
scope:{name:"#"}
That won't work because ng-repeat creates an isolated scope for each element it iterates. So I ended up having two levels of scopes.
How do I get around this issue? Am I doing something wrong?
Thanks.
As I stated in my comment of your original question, this has already been answered. Anyway, here it is, summed up:
In your template, state the model you want to have inherited, without {{}} (as using brackets results in the value being passed, and not the reference to the model itself):
<mycomp name="obj.name" ng-repeat="obj in list" />
And in your directive, establish a 2-way binding, like so:
scope:{name:"="}
EDIT:
I realize now (after your comment) that while this solves your problem, it doesn't fully answer the question. Here goes:
When you create a directive you have the choice of creating a scope that inherits from its parent (controller, typically, though not necessarily) ou an "isolated" scope, by specifying scope: true or scope: {...}, respectively.
So, by creating an unisolated scope, all the parent's models are available (you can access scope.obj - created via ng-repeat - but also scope.list). This is convenient, but also dangerous, of course (and doesn't really create reusable code).
If you create an isolated scope, you can specify the scope's models using '#', '=' or '&'.
'#' and '&' both produce a isolated, unbinded value, (that, if you change, changes only on the isolated scope - in your case, the object in the original list suffers no change at all), the only difference being that '#' reads a string value, and '&' reads an expression.
THIS IS IMPORTANT: the reason why I believe your code didn't work was (only) because you passed name="{obj.name}" and not name="{{obj.name}}", for with '#' the string value is read, and that string value can be the name of obj, but you must include it in {{}}!
If you use '=', you are declaring that you want that variable to be binded with the specified outside variable. So, if (in a fit of crazy, crazy rage!) you want to have 2 models in your directive that start up with the same value, but on is binded (i.e. changes are propagated to the outside scope), you could do something like this:
<mycomp binded-name="obj.name" unbinded-name="{{obj.name}}" ng-repeat="obj in list" />
and in your directive:
scope:{
bindedName: "=",
unbindedName: "#"
}