Using AngularJS 1.0.8, I'm trying to create some reusable directives to create a situation where a web developer can code a single "top-level" directive with a number of attributes, and this directive, in turn, has a template containing other directives, which themselves might contain other directives etc.
The problem I'm having is making the "inner" templates aware of the top level attributes. I thought this would be a universal problem, but it didn't look, from my research, that anyone else was asking this.
I created this Plunker to show the problem:
<!DOCTYPE html>
<html ng-app="outerInnerDirectivesApp">
<head>
<title>Outer/Inner Directives</title>
</head>
<body>
<div>Single level directive follows:</div>
<single-level-directive single-level-id="single123"></single-level-directive>
<div>Outer/inner directive follows (Expecting "outer123"):</div>
<outer-directive outer-id="outer123"></outer-directive>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script src="app.js"></script>
<script src="directives.js"></script>
</body>
</html>
In the Plunker,
single-level-directive works and is, I think, a standard way to display data.
outer-directive and inner-directive aren't working.
What I expected to happen with these was
(i) outerDirective compiles/links to produce the html
<inner-directive inner-id="outer123"></inner-directive>
and then
(ii) innerDirective compiles/links to produce html
<div>outer123</div>
But at step (ii) I get
<inner-directive inner-id="" class="ng-isolate-scope ng-scope">
<div class="ng-binding"></div>
</inner-directive>
so an empty div is generated by innerDirective.
In fact, if I change outer-template.html to be
<div>{{outerId}}<div>
then the value displays correctly, so it looks like scope.outerId is available at the correct point, but Angular isn't happy about me trying to use it in the way I am.
Is this a reasonable thing to expect Angular to do? If so, what am I missing? If not, then what do you think would be a sensible alternative way to build up more complex screens from simple sets of directives?
If you are going to design directives with isolated scope, I would suggest using the isolated scope to define the type of attribute you want to use:
outerInnerApp.directive("outerDirective", function() {
return {
restrict: "E",
scope: {
outerId: '#'
},
link: function(scope, element, attrs) {
},
templateUrl: "outer-template.html"
};
});
outerInnerApp.directive("innerDirective", function() {
return {
restrict: "E",
scope: {
innerId: '='
},
link: function(scope, element, attrs) {
},
templateUrl: "inner-template.html"
};
});
Here is a working plunker.
Your outer directive is using the value that is defined in the attribute. So, to pass the value into the isolated scope, we can use #. The inner scope is then binding a variable through. So, we can use = to set up a bound attribute.
Had some more thoughts about this. Having used AngularJS a bit more, I'm not sure that I want to bind to the scope (using the "="). In fact, I can get the original Plunkr to work by making these changes:
outerInnerApp.directive("outerDirective", function() {
return {
restrict: "E",
scope: {
//add outerId here
outerId: "#"
},
link: function(scope, element, attrs) {
//remove scope assignment here
//scope.outerId = attrs.outerId;
},
templateUrl: "outer-template.html"
};
});
outerInnerApp.directive("innerDirective", function() {
return {
restrict: "E",
scope: {
//add innerId here
innerId: "#"
},
link: function(scope, element, attrs) {
//remove scope assignment here
//scope.innerId = attrs.innerId;
},
templateUrl: "inner-template.html"
};
});
What I don't understand at the moment is why there is a different between, say,
innerId:"#"
and setting the value of scope in the link function
link: function(scope, element, attrs) {
scope.innerId = attrs.innerId;
}
When I find out why it behaves differently I'll post back.
Related
How can I parse/compile/resolve the contents of contained html in my directives.
The directive in question is:
angular.module('transclude', [])
.directive('heading', function(){
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
cls: '#'
},
template: '<h1 ng-transclude></h1>'
};
});
and the html to accompany it:
<div ng-app="transclude">
<div ng-controller="Ctrl">
<heading cls="beans">
<span class="{{cls}}">{{cls}}</span>
</heading>
</div>
</div>
I've created the following very simplified plunker to demonstrate my problem: http://jsfiddle.net/ys9fekss/
As you can see, I am expecting the html which is contained within my directive to have the {{cls}} tags on both the attribute and the contained html to be replaced with the literal string 'beans'.
I've been struggling with this all day - I've looked at scoping, compiling, link functions - you name it, and to no avail.
What I'm trying to do is create a validator directive which can wrap any type of field.
What do I have to do to get angular to parse that field's html?
UPDATE: Since I'm still struggling with this, I've posted my actual HTML (modified for the solution given below) showing the problem with the shared scope:
http://jsfiddle.net/hwsqsxf3/
In the above example, setting scope: true stops the repeated values, but then stops the name="" attribute being parsed!
What I need is both of these things... at once!
perhaps a different aproach is what you need, a directive with shared scope (not isolated like yours) but with the ability to create new values to the scope, that way you can use the directive many times in your app but with different values in each one of them:
<div ng-app="transclude">
<div ng-controller="Ctrl">
<heading cls="beans" target="tar">
<span class="{{tar}}">{{tar}}</span>
</heading>
</div>
</div>
// the target attribute will be your scope value (theres no need to be even declared in the controller)
function Ctrl($scope) {
$scope.foo = 'Bar';
}
angular.module('transclude', [])
.directive('heading', function(){
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<h1 ng-transclude></h1>',
link: function(scope, el, attr){
scope[attr.target] = attr.cls;
}
};
});
check this fiddle
I think I've solved my own problem - with much help from Strife86 and This blog
My solution is here. It's not very elegant - not sure if this is a fault of the way directives work or a fault of me. If there is a more elegant way I'd love to hear it.
JsFiddle
The directive's code is bascally:
.directive('validatingField', ['$compile', function($compile) {
return {
restrict: 'EA',
transclude: true,
scope: {
label: '#',
field: '#',
labelWidth: '#',
fieldWidth: '#',
error: '=',
ngModel: '='
},
templateUrl: "validating-field.html",
link: function (scope, element, attrs, ctrl, transcludeFn) {
transcludeFn(scope, function(clone, scp) {
element.find('ng-transclude').replaceWith(clone);
});
}
};
}]);
Binding ngModel was the most difficult thing and I'm not happy repeating elements constantly, but right now I've burned 1.5 days to get here so I'm not really in the mood for experimenting!
Ok, this seems to be the fix, although it is a bit inelegant:
http://jsfiddle.net/ywksgocm/
This includes two-way binding of the model, and inherited and augmented parent scope (for abritrary controller scope access).
The key to this is:
myScope.$new();
If there are any ways to improve this, please do let me know.
I'm having two directives with same functionality like below.
angular.module('ui.directives', [])
.directive('uiFoo',
function() {
return {
restrict: 'EAC',
link: function($scope, element, attrs) {
//to do functionality
element.append("test content");
}
};
})
.directive('uiFoo1',
function() {
return {
restrict: 'EAC',
link: function($scope, element, attrs) {
//to do functionality
element.append("test content");
}
};
});
They both contain the same working like here it is appending "test content" as text to that element.
Is there any chance that instead making this two directive. Can i write two names for one directive/ I can use same functionality in with optimize code.
Here I'm writing same code for without any meaning.
Instead of writing directive two times, is there any optimize way.
I'm new in AngularJS kindly help me.
Thanks in advance!
The simpliest way would be to extract your directive into a JS object and use this instead.
Alternativly you can provide the directive object with a angular provider, if you want to stay in the angular context.
But, why do you want to have two directives with the exact same functionality in the first place?
Directives can used as often as you want, so this seems like a design flaw to me.
var myDirective = [function(){
restrict: 'EAC',
link: function($scope, element, attrs) {
//to do functionality
element.append("test content");
}
}];
angular.module('ui.directives', [])
.directive('uiFoo', myDirective)
.directive('uiFoo1', myDirective);
I'm receiving the error: Multiple directives [gridsection, gridsection] asking for templateon : <div gridsection=""> with this code.
I don't see how i'm using nested directives or what is causing this.
html page
<div gridsection ></div>
directive
angular.module('web').directive('gridsection', function() {
return {
restrict: 'A',
replace: false,
scope: {
patient: "=patient"
},
templateUrl: 'directive/section.html',
link: function(scope, element, attrs, fn) {
}
};
});
directive/section.html
<div>
here?
</div>
It seems like you are declaring the gridsection multiple times in your angular code.
I have seen this before when I have a copy of a directive script file in a folder.
i.e. my file structure was
* myDirective.js
* myDirective - copy.js
So essentially I had two directives with the same name.
Doh!
Note originally posted this as a comment but created as an answer in response to comment from #jayjayjay
For posterity, I was getting this exception because I was trying to create a directive named pager and that was colliding with Bootstrap's pager.
Make sure you didn't include the script tag twice.
I had this issue but only declared the directive once in the markup, turns out it was because I included the script twice.
Note: I saw this in one of the comments for another answer and posted it as an answer for easier access/to prevent it getting lost.
I was getting this error for a reason not specified in other answers.
I was using declaration for xyz directive as <xyz xyz="xyz"></xyz>
my definition was:
angular.module('app')
.directive('xyz', function () {
return {
templateUrl: '..../xyz.html',
restrict: 'EA',
scope: {
xyz: '='
},
link: function (scope, element, attrs) {
}
};
});
The problem here is that I allowed directive to be used as element and attribute. so <xyz xyz="xyz"></xyz> contained both the declaration which was causing the issue.
Solution is to either restrict the directive to be used as Element only restrict: 'E' OR change the name of the directive to something like xyzView and use it like <xyz-view xyz="xyz"></xyz-view>.
How do you create a 2 way binding with a nested property in an isolate scope with dotted notation. I thought 'myObject.data': "=data" would work, but it does not. I don't want to link everything in the myObject object. I know I could do some sort of watch, but 'myObject.data' seems cleaner.
.directive("myDirective", [function() {
return {
restrict: "E",
scope: {
'myObject.data': "=data"
},
link: function (scope, element, attrs) {
scope.myObject = {
data: "myValue"
};
}
};
}])
Isolated scopes are generally useful only with templates, they should not be used as a way to declare how you want your directive attributes to be interpreted. This is because most directives that don't have a template usually need the semantics of either a child scope or the direct scope of their environment.
In your case, you probably don't even need a $watch, because object references are what enable 2 way data binding, but without your full code I cannot be sure.
In case you want to know the translations for an isolated scope semantics to just a normal one:
#name -> attrs.name
=name -> $scope.$eval(attrs.name);
&name -> function() { return $scope.$eval(attrs.name); }
EDIT 2:
After your comment, I came up with this plunker. To preserve two way data binding you have to use a "." in your ng-model declaration. This is because two way data binding does not work for value types, since they are immutable. You can't change the value of 100 for example. You need to pass around a reference type object and hang the values you are changing off of it. Your desire to specify the full path to the value in the isolated scope definition is not possible based on the principles that two way data binding is made possible by.
Javascript:
angular.module('plunker', [])
.directive('twoWay', function() {
return {
restrict: 'E',
template: '<div><input ng-model="thing.name" type="text" /></div>',
scope: {
thing: "="
},
link: function(scope, element, attrs) {
}
};
})
.controller('MainCtrl', function($scope) {
$scope.data = {
name: "World"
};
});
HTML:
<body ng-controller="MainCtrl">
<p>Hello {{data.name}}!</p>
<two-way thing="data"></two-way>
</body>
What I use in these cases is the following:
.directive("myDirective", [function() {
return {
restrict: "E",
scope: {
data: "="
},
controller: function($scope){
$scope.dot = $scope //<--- here is the trick
}
};
}])
Then you can always change data in the directive's scope from an inherited scope through dot.data = 'whatever' without setting watchers.
Not very elegant but it works jsut fine in cases where you are not using the controller as syntax and don't want a $parent nightmare.
I have a directive which looks something like:
var myApp = angular.module('myApp',[])
.directive("test", function() {
return {
template: '<button ng-click="setValue()">Set value</button>',
require: 'ngModel',
link: function(scope, iElement, iAttrs, ngModel) {
scope.setValue = function(){
ngModel.$setViewValue(iAttrs.setTo);
}
}
};
});
The problem is that if I use this directive multiple times in a page then setValue only gets called on the last declared directive. The obvious solution is to isolate the scope using scope: {} but then the ngModel isn't accessible outside the directive.
Here is a JSFiddle of my code: http://jsfiddle.net/kMybm/3/
For this scenario ngModel probably isn't the right solution. That's mostly for binding values to forms to doing things like marking them dirty and validation...
Here you could just use a two way binding from an isolated scope, like so:
app.directive('test', function() {
return {
restrict: 'E',
scope: {
target: '=target',
setTo: '#setTo'
},
template: '<button ng-click="setValue()">Set value</button>',
controller: function($scope) {
$scope.setValue = function() {
$scope.target = $scope.setTo;
};
//HACK: to get rid of strange behavior mentioned in comments
$scope.$watch('target',function(){});
}
};
});
All you need to do is add scope: true to your directive hash. That makes a new inheriting child scope for each instance of your directive, instead of continually overwriting "setValue" on whatever scope is already in play.
And you're right about isolate scope. My advice to newbies is just don't use it ever.
Response to comment:
I understand the question better now. When you set a value via an expression, it sets it in the most immediate scope. So what people typically do with Angular is they read and mutate values instead of overwriting values. This entails containing things in some structure like an Object or Array.
See updated fiddle:
http://jsfiddle.net/kMybm/20/
("foo" would normally go in a controller hooked up via ngController.)
Another option, if you really want to do it "scopeless", is to not use ng-click and just handle click yourself.
http://jsfiddle.net/WnU6z/8/