I am struggling to get access to an isolated scope from some html I am building in the compile function of a directive. I have a SIMPLE example, the one I am working with is much more complex. Essentially I can't access properties on the isolated scope. Here is the code.
http://plnkr.co/edit/ETIRs4j3EwZ4DFN5XkPc?p=preview
It's odd to me because the in the console the scope looks great, from the post function.
Notice how the html is set up in the compile function:
tElement.append(angular.element('<div>{{name}}</div><span>{{value}}</span><span>Hello</span>'))
Also notice the second directive, Here with an inherited scope everything works fine.
Edit: I updated the first directive(The directive that isn't working) to set a property in the html so I could look for it in the console when I log the scope. It turns out it is on the $parent scope (The property is named "wat"). But why? It makes sense why it wouldn't work, but I don't understand why that html doesn't have access to the same isolated scope? Is there a way to force the html I am inserting to use the isolated scope instead?
Edit 2: Ok so a lot of questions about why I am trying to do this, the best description could be found here. https://github.com/angular/angular.js/issues/7874. Essentially we wanted to ng-repeat some transcluded content, and expose the item from ng-repeat to the transcluded content, but you can't do that in angular, because the transcluded content only has access to the parent scope.
The goal is to get the first directive to work like the second, in that the value of the two properties (value, and name) work from appending html in the compile function, with an isolated scope.
This is how I would go about what ( I think ) you are trying to achieve
http://plnkr.co/edit/mL7h7Hf8TkkpfsPNoL6g?p=preview
By using a template with transclude: true and a link function you can achieve the desired result as you replace the element with your template while interpreting the angular expressions
You could use replace: true to further customise your directive
app.directive('widget',function(){
return {
restrict:'E',
transclude: true,
template: '<span ng-bind="wat=\'YES\'"></span><div>{{name}}</div><span>{{value}}</span> <span>There</span>',
scope:{
name:'#',
value:'='
},
link:function($scope, tElement){
}
}
});
Related
I'm trying to implement a d3 directive in Angular, and it's hard because visually nothing is happening, and no errors are being thrown on the console.
Here's my d3 directive:
myApp.directive('d3-bars', ['d3Service', function($window, d3Service) {
return {
restrict: 'EA',
scope: {},
link: function(scope, element, attrs) {
// More code below ....
Here is my HTML:
<d3-bars bar-height="20" bar-padding="5"></d3-bars>
At first I thought it wasn't appending an svg, because inspecting the element that's what it looks like, but now I don't think the directive is even running at all. I stuck a console.log inside of it at the very beginning and it didn't appear either. Am I missing something simple?
EDIT:
I tried changing the top line to
angular.module('myApp.directives', ['d3'])
.directive('d3-bars', ['d3Service', function($window, d3Service) {
But that didn't work either. I don't even know what's the difference between the two headers anyway...
Your directive name may be wrong. Angular directives are commonly camel-cased. And when in the HTML they are hypenated. so ngClass turns into ng-class in the HTML.
At least when I've tried to use - or other characters in my directives it hasn't worked.
Check out this Google Group post for some validity: using dash in directive
Also here are the docs: Directives - matching directives
You'll also want to make the change that was suggested in the comments by JoshSGman:
.directive('d3Bars',['$window', 'd3Service', function($window, d3Service) {
the naming of your directive is the problem. Angular normalizes the names of directives in the html before it matches them to the names in JavaScript. The normalization process works in two steps:
Strip x- and data- from the front of the element/attributes.
Convert the colon-, hyphen-, or underscore-delimited name to camelCase.
So, the correct name for your directive in JavaScript would be d3Bars. Change it to that and it should work.
See https://docs.angularjs.org/guide/directive#matching-directives for more information.
I've had similar behavior when I forgot to define the link property.
No errors in the console, nothin.
One more thing happened to me that made the directive not to work at all
when you have one module have a directive and you created a new module with a new directive but due to the copy-paste you forget change Module name, this way AngulerJs somehow will remove the first directive
so
make sure that every module have unique name
I'm trying to implement a d3 directive in Angular, and it's hard because visually nothing is happening, and no errors are being thrown on the console.
Here's my d3 directive:
myApp.directive('d3-bars', ['d3Service', function($window, d3Service) {
return {
restrict: 'EA',
scope: {},
link: function(scope, element, attrs) {
// More code below ....
Here is my HTML:
<d3-bars bar-height="20" bar-padding="5"></d3-bars>
At first I thought it wasn't appending an svg, because inspecting the element that's what it looks like, but now I don't think the directive is even running at all. I stuck a console.log inside of it at the very beginning and it didn't appear either. Am I missing something simple?
EDIT:
I tried changing the top line to
angular.module('myApp.directives', ['d3'])
.directive('d3-bars', ['d3Service', function($window, d3Service) {
But that didn't work either. I don't even know what's the difference between the two headers anyway...
Your directive name may be wrong. Angular directives are commonly camel-cased. And when in the HTML they are hypenated. so ngClass turns into ng-class in the HTML.
At least when I've tried to use - or other characters in my directives it hasn't worked.
Check out this Google Group post for some validity: using dash in directive
Also here are the docs: Directives - matching directives
You'll also want to make the change that was suggested in the comments by JoshSGman:
.directive('d3Bars',['$window', 'd3Service', function($window, d3Service) {
the naming of your directive is the problem. Angular normalizes the names of directives in the html before it matches them to the names in JavaScript. The normalization process works in two steps:
Strip x- and data- from the front of the element/attributes.
Convert the colon-, hyphen-, or underscore-delimited name to camelCase.
So, the correct name for your directive in JavaScript would be d3Bars. Change it to that and it should work.
See https://docs.angularjs.org/guide/directive#matching-directives for more information.
I've had similar behavior when I forgot to define the link property.
No errors in the console, nothin.
One more thing happened to me that made the directive not to work at all
when you have one module have a directive and you created a new module with a new directive but due to the copy-paste you forget change Module name, this way AngulerJs somehow will remove the first directive
so
make sure that every module have unique name
I have a problem when creating multiple directives with isolated scope: when I change something in 1st directive it also makes changes in all other directives.
Here is a working example: http://plnkr.co/edit/drBghqHHx2qz20fT91mi?p=preview
(try to add more of Type1 'Available notifications' - a change in 1st will reflect in all other directives of Type1)
I found some solutions to similar problems here but they don't work in my case. Also found a working solution with mapping 'subscription' data to local scope variables in directive (app.js, line 76) but I think there should be a more general way to do this right?
In your directive 'notificationitem' you have the following code, keep it in mind as i explan:
// if all variables are mapped in this way than works
//$scope.enabled = $scope.subscription.enabled;
The reason why all of the 'isolated' scopes are updating is because of this code in your scope declaration in the same directive (notificationitem):
scope: {
subscription: '=',
index: '#'
},
The equal sign on subscription is angular's way of saying "Whenever the current scope updates, go to the parent and update that value as well." This means whenever you update your 'isolated' scope, it updates the parent scope as well. Since all of these isolated scopes are binding to the parent, they will change as well.
Since you want the subscription.value to be the default value of that text field, you will need to do exactly what your commented code is doing:
scope.value = scope.subscription.value;
This will create an isolated value inside of the isolated scope. When scope.value changes, scope.subscription.value will not. All of the text fields now have their own 'value' to keep track of.
Check out this article for information on directive bindings: http://www.ng-newsletter.com/posts/directives.html
Also, another way to get the default value would be to inject your service into the directive, if you don't like the above solution. Hope this all helps.
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: "#"
}