i have this very simple directive.
you can find the code here
In the code,
i have used '#' for link in the scope.
I am able to get it correctly.
But this is not two way binding, so i tried to do it with "=" in scope.
This part does not seems to reflect in my template. I tried to do see if the link variable
is present in scope,it seems to be undefined.
Should this directive be placed inside a controller?
What is that i am missing in my code.
You seem to be missing the difference between the # and = bindings. While the 2 might look similar those are fundamentally different ways of bridging "directive world" with the "page world".
Firstly, let's start with the similarities: both types of binding allows you to pass data from a page that is using a directive to the directive itself (directive intenal scope). But this is where similarities end, and the list of differences goes like this:
= is the 2-way data binding that can cross page / directive world in both ways: from a page to a directive and from the directive scope to the page scope. # on the other hand only allows you to pass data from a page to the directive and not from the directive to the page.
= binding allows you pass data defined on scopes - that is - any JavaScript variable (primitives, arrays, objects). # is different and is passing data through a DOM attribute. As such those attributes are restricted to Strings only.
given the above, the = and # are also triggered differently from the page that is using a directive: for = we need to pass an expression that points to data defined on the scope
while with # we are going through the DOM and need to use the interpolation directive ({{foo}}) to access data available on the scope.
After all those explanations you can see that using = in the directive definition we need to pass an expression so if you do this: <mydirective link="link1" group="main"></mydirective> you are effectively saying: pass to the directive a value of the link1 variable defined on a scope. Since such variable is not defined you are passing undefined to the directive.
So, if you intend to pass a constant (which I assume you want to do), you will need to write:
<mydirective link="'link1'" group="main"></mydirective>
Here is a working plunk: http://plnkr.co/edit/M3qL4MdmoWjTWzZGkwz0?p=preview
One thing most people forget is that you can't just declare an isolated scope with the object notation and expect parent scope properties to be bound. These bindings only work if attributes have been declared through which the binding 'magic' works. See for more information:
https://umur.io/angularjs-directives-using-isolated-scope-with-attributes/
Related
Trying to have 2 way binding on an AngularJS directive while using primitive objects is not working, for example:
<custom-directive ng-model="variable"></custom-directive>
how can this be achieved?
In order to have 2 way binding in javascript (not just angularjs), we have to pass an object (this is caused by javascript's evaluation strategy - can read more about it here https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing). basically what is happening is that when we pass a primitive variable, its been passed by value and re-created, instead of been passed by reference. only objects are passed by reference.
So this issue can be solved by passing the variable's parent object, for example:
<custom-directive ng-model-name="variable" ng-model-parent="parentObj"></custom-directive>
and then, modifying in object in the directive as following:
parentObj[variable] = "whatever";
this way, we will keep the connection between the variable to the parentObj.
another option would be passing the model with the parent obj, for example:
<custom-directive ng-model="parentObj.variable"></custom-directive>
the dot is an important part of this example. its actually a best practice by angular to always pass variables with the parentObj-dot-property.
for additional information, angularjs actually has a documentation about it https://github.com/angular/angular.js/wiki/Understanding-Scopes
I just realized that if your directive isn't inside an ng-if it will work with primitive bindings. Maybe the problem is that your bind is inside an ng-if. Try to use ng-show instead. Maybe it will work.
Passing the primitive this way:
<custom-directive ng-model="parentObj.variable"></custom-directive>
Preface
When I declare a directive under a controller, e.g.
<div ng-controller="MyController">
<my-directive></my-directive>
</div>
the directive inherits that controller's scope by default. This means if the controller defines
$scope.Heaven = "Free Beer"
then I have access to that within the directive's template via
{{ Heaven }}
Question
When declaring a directive within another directive, why doesn't the child inherit scope like it would if placed in a controller?
<my-parent-directive>
<my-child-directive>
</my-child-directive>
</my-parent-directive>
In short, if I declare a controller function for my-parent-directive and in it write:
$scope.Heaven = "Free Beer"
My child directive doesn't have access to this by default. Why is that?
(This assumes "scope: true" within the parent, no scope declaration in the child, and the child requiring the parent via "require: 'my-parent-directive')
Example codepens:
Directive wrapped in controller
Directive wrapped in directive
Question was modified after answer was given - the below is to preserve the reference
Directive wrapped in directive old
I am looking at the "Directive wrapped in directive old" on codepen. I think it is this you want to fix, but I'm not certain since your codepen is different to the example in your question (that's not a criticism, just clarification in case I am heading down the wrong route for you!)
However, if I am correct (and I am referring to the "Directive wrapped in directive old" on codepen for the rest of this answer):
You have declared the scope in myWrapper to be inherited ("scope: true"), therefore any properties that you add to the scope within myWrapper (such as "$scope.passdown = $attrs.passdown;") will only be visible to myWrapper.
You can remove the "scope: true" from myWrapper to share the scope between everything (not a great structure to use, but it will work) and you will solve your immediate problem, if I have understood you correctly. Or you can move the "passdown" property to a mutable object on the "parent" controller "$scope.abc = {passdown: ''};" for example. Then modify the value in myWrapper: "$scope.abc.passdown = $attrs.passdown;" and access it as "abc.passdown" in your interpolated expressions.
a bit of background:
changes to immutable types in "child" controllers/directive will make a copy of the property and those changes will never be seen on any other scope.
No scope declaration means shared scope - all components that share this scope too can see any properties / changes (to mutables) made on the scope. Tends to end up with closely coupled components that become very difficult to maintain.
"scope: true" means inherited scope and any additions made to the scope will only be visible to the inherting scope (ie the "child"). Changes to mutable properties in the parent will be visible to all other components that share this scope.
"scope: {...}" creates an isolated scope and provides a safe way to expose properties to parents and let the children modify those properties. This implementation is more work but you will end up with code that is easier to understand, maintain and share.
I hope this answer isn't too rambling and it helps you solve your problem.
These are the ways i tried to require one directive's controller in another one.
1) http://jsfiddle.net/Xarm2/1/
2) http://jsfiddle.net/82UKq/1/
In both the cases you can see in the firebug console, that the required directive's controller is either undefined or an empty object.
1) How can i access 'd1Cntrl' inside the d2 directive.
2)Please let me know one use case that will need me to require an directive controller in another directive.
See this nifty screencast from John Lindquist on directive to directive communication
For example, if you have an input element where you would like to apply two custom validations where one validation should only run if first validation passes (e.g. pattern matching validation should only activate if element has a value).
Few thinks i could like to put it here
1) Directives are not creating their own scope, For ex.
<superhero strength>The Hulk</superhero>
<superhero flight speed strength>Superman</superhero>
<superhero speed>The Flash</superhero>
these three directives will share the same scope, unless we explicitly say scope:true or scope:{} in the superhero definition. If you we are not creating new scope, then the last superhero will be in effect.
2) If we create a new scope at superhero level, that same scope is shared by the sibling directives like strength, flight etc..
3) If we require an directive controller inside another directive, the api that is defined on the directives controller using this keyword will be exposed to the requiring directive. Please note that the methods that are defined in the controller scope $scope wont be visible.
Please feel free to edit if the sentences making is wrong or the points were wrong.
Related Post, but didn't help:
Scoping issue when setting ngModel from a directive
EDIT: Can I use ng-model with isolated scope? didn't work either.
I got the some problem but in a more complex way I guess. I want to write a pulldown that does not use any input for data saving. I'd rather have the ngModel to take care of it.
http://jsfiddle.net/QeM6g/6/
The jsFiddle example above shows a demo where the above described methods didn't work.
// this is what should work but doesn't
ngModel.$setViewValue(value);
scope.$apply(attr.ngModel,value);
For some reason the scope of the ngModelController is a sibling of my scope. so it doesn't pass the changes back to the parent. at least all other sibling scopes behave as you'd expect. i.e. ng-change works in combination.
Angularjs doesn't deal very well with direct bindings to primitive types.
If you change this line:
$scope.productId = 16;
to something like this:
$scope.selectedProduct = {
id: 16
}
and change those references on the rest of the code, you should be able to overcome the issue.
jsFiddle: http://jsfiddle.net/M2cL7/
Don't bind to primitives in a scope, bind to an object in the scope.
From https://github.com/angular/angular.js/wiki/Understanding-Scopes
... until you try 2-way data binding
(i.e., form elements, ng-model) to a primitive (e.g., number, string,
boolean) defined on the parent scope from inside the child scope. It
doesn't work the way most people expect it should work. What happens
is that the child scope gets its own property that hides/shadows the
parent property of the same name. This is not something AngularJS is
doing – this is how JavaScript prototypal inheritance works. New
AngularJS developers often do not realize that ng-repeat, ng-switch,
ng-view and ng-include all create new child scopes, so the problem
often shows up when these directives are involved.
This issue with primitives can be easily avoided by following the
"best practice" of always have a '.' in your ng-models
so
<input ng-model="tweetText">
becomes
<input ng-model="tweet.text">
A great summary is here:
https://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m
I have a control that should display breadcrumb navigation. It needs data (route & title) to display the navigation correctly. Data is taken from scope and used inside a directive.
What causes my problems is that I use a localization directive in the control that should translate the title. And this localization directive is called even when expression in ng-show is evaluated to false. Then the translation in localization directive ends with exception because it tries to translate incorrect string (see 'localize' directive in http://jsfiddle.net/F97wn/7/).
That seems quite weird. I would expect that if something sets whether the inner content should be visible or hidden, then it is evaluated first and then the inner content..
Ok, then I found that ng-show only sets some css attribute, so it's quite useless for me.
The question is: How should I solve the problem - what to use instead of the ng-show?
An example is at http://jsfiddle.net/F97wn/7/
You could use ngSwitch instead with the on part set to "toshow()" and the inner ng-switch-when="true" part to have your custom directive inside that area. This will then not execute the custom directive if the value of toshow is not true.
If the directive is throwing an exception, more information should probably be passed to the directive, in one of the following ways, so that the directive can decide if it has the required information to do what it needs to do:
attribute data -- e.g., localize="..." show-me="..."
something defined on the scope associated with ctrl -- e.g., $scope.showMe. The directive scope will have access to this property as scope.showMe, based on the way you currently have the directive defined.
or inject a service (that has the data) into the directive -- e.g., directive('localize', function(myShowMeService) { ... }
You might also want to look into <ng-if>. The ngIf directive removes and recreates a portion of the DOM tree (HTML) conditionally based on "falsy" and "truthy" values evaluated within an expression. It might be more intuitive than <ng-show> for your needs.
However, it is currently only available in the unstable version of AngularJS. If you can use that version of AngularJS you can find more information about it here.