Pretend I have this:
<span ng-bind="value" my-dir my-dir-param="value"></span>
So my-dir is a custom directive that adds another directive to an existing element.
I guess it gonna look like this (skipping everything except some props from dir. def. object:
...
priority: 1001,
terminal: true
compile: function(el, attrs) {
attrs.$set("some_directive", "placeholder_text");
var compiled = $compile(el, null, 1001);
return function linkFn(scope, el, attrs) {
compiled(scope)
...some extra logic here...
}
}
...
I've tried to do such away and the problem is that ng-bind will bind on value from the directive scope, not the outer scope. I don't want to affect scopes from which other directives absorb values. What I want is that by adding my-dir directive I will apply "some_directive" and that won't affect the values that other directives receive. Thank you
Related
I would like to create a directive that has transcluded content that the directive can bind to and modify. The directive has an isolate scope. I imagine it working something like this:
<my-directive bound-item-name="childObj">
<input ng-model="childObj.someField">
</my-directive>
At runtime, I want to use childObj as an alias for an object on my-directive's isolate scope called activeObject. Essentially, you might think of this as similar to the way ng-repeat lets you use a statment like obj as alias in objList and in the transcluded content alias refers to the individual instance.
I can't seem to figure out how I can actually do this... if I change the transluded content to refer to $parent.activeItem it does work the way I intended, but I feel like that's expecting the transcluded content to know too much about how the directive works. It seems like modifying in the compile function might work, except I can't see, in the docs, how I can actually do that with the transcluded content. Forcing the transcluded content to share its scope with the directive would be OK, although I see no evidence that there's some way to do that.
This must be possible, but how?
Fiddling around with this some more, I am able to get it to work by modifying scope.$$childHead[scope.boundItemName] instead of using scope.activeObject in the directive. While this works I'd like to not rely on undocumented internal objects, if possible.
The link function of the directive is given the transclude function as the 5th parameter.
link: function(scope, element, attrs, ctrls, transclude){
// ...
}
This transclude function takes a scope variable that you can create and another function - called "clone linking function" - that places the pre-linked transcluded content in the DOM. The transclude function links against that scope variable that you provided.
Here's how it works.
transclude: true,
scope: {}, // you are free to use whatever scope you need
link: function(scope, element, attrs, ctrls, transclude){
var boundObj = {}; // your object
var alias = attrs.boundItemName;
// let's create an isolate scope for the transcluded content
var newScope = scope.$new(true);
newScope[alias] = boundObj;
transclude(newScope, function(preLinkContent){
element.append(preLinkContent);
});
}
Then, if you used your example:
<my-directive bound-item-name="foo">
<input ng-model="foo.text">
</my-directive>
Then, the transcluded ng-model would write into your internal boundObj's .text property.
Demo
I have a directive whose 'config' attribute value I need to access inside my directive controller.
Since the controller constructor get executed first,communication from controller to link is possible but not vice versa.
What should be the best way to achieve this?
I have considered the following approaches
1)Add the variable to scope-
That would in my opinion pollute the scope,making the variable accessible every where else where the scope is being shared.
2)Use $broadcast
Again the same issue as above
3) Pass a callback function on controller's this and call it from the link function with config as its argument
4)Pass the value through a service- In my case I have multiple such directives that would need to pass date through this service
Or is there some better approach that I am missing out for doing this?
module.directive('myDirective',function(){
return{
restrict:'E',
templateUrl:'path/to/html',
link:function(scope,iElement,iAttrs,controller){
var config=iAttrs.config;
//How to access this value inside the directive controller?
},
controller:function($scope){
//the directive attribute 'config' is required here for some larger computations which are not
//manipulating the DOM and hence should be seperated from the link function
})
There you can use isolated scope concept where you create isolated scope inside your controller & that would not be prototypically inherited from its parent scope. For that you need to use scope: { ... } inside your directive option. There are three options to pass scope value inside a directive through attribute
# : One way binding
= : Two way binding
& : Expression
In your case first two cases would be fine that are depends which one you need to use. If you just want to pass the value of scope variable to the directive in that case you could use 1st approach which would be # one way binding.
If you want to update the variable in both directive as well as controller from where it come i.e. nothing but two way binding, then you need to use =
I think = suits in your case so you should go for =
Markup
<my-directive config="config"></my-directive>
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
config: '='
},
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope) {
console.log($scope.config); //here also it would be available inisde scope
//you could put a watch to detect a changes on config
}
}
});
Demo Plunkr
Update
As config value has been provide from the attribute with expression like {{}} so we could get those changes inside controller by putting [**$observe**][2] on $attrs. For that you need to inject $attrs dependency on your controller that will give you all the attributes collection which are available on directive element. And on the same $attrs object we gonna put $observe which work same as that of $watch which does dirty checking & if value gets change it fires that watch.
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope,$attrs) {
//you could put a watch to detect a changes on config
$attrs.$observe('config', function(newV){
console.log(newV);
})
}
}
});
Updated Demo
I have a directive where a list(array) of items is passed in through the scope of the controller into the scope of the directive whereby the template of the directive then has access to the items.
I would like to have it so that the list of items is passed to the directive (where it is then used within the link function) and then not directly accessible through the directive's template.
i.e. if we had the following directive:
directive('itemList', function(){
return {
scope: {
items: '='
}
link: function(scope, elem, attrs){
var item-list = scope.items;
// ... want to manipulate first ...
}
}
})
the variable scope.items is now available to any template that the directive uses. Whereas I don't want that to be the case and would like to pass in something to the directive without making it known to the scope. Is this possible?
Since the directive scope pretty much by definition is the scope used by the directive's template, I don't see any obvious way of doing this in a strict information hiding way. But, why not just use a different name for the passed scope variable and what the template binds to? For example, if you said scope: { myPrivatePassedItems: '=items' }, you can now work with scope.myPrivatePassedItems as much as needed before setting it as scope.items. With this approach, the HTML in both the usage of the directive and the directive's template just sees "items", but internally your directive has its own "private" variable.
I should add that the above is the simple change needed for the one-way data flow from the consumer to the directive template. If you also need to update the original items array, you will also want to add a scope.$watch on the scope.items array after you have done your initial setup. You would then need to carry those changes (possibly with modifications) back to scope.myPrivatePassedItems in order to update the consumer's array.
You can use the $parse service to retrieve the value without using scope: {}, but you will lose the 2 way data binding that you inherently get from using scope: { items: '=' }. As mentioned by Dana Cartwright, if you need 2 way binding, you have to set this up manually with watches.
directive('itemList', function($parse){
return {
link: function(scope, elem, attrs){
var itemList = $parse(attrs['items'])(scope);
// do stuff with itemList
// ...
// then expose it
scope.itemList = itemList;
}
};
});
I found a great tree directive here. Original: http://jsfiddle.net/n8dPm/
I have been trying to understand the functioning of it through couple of other SO questions, 1,2 . I couldn't quite understand how the recursive calls to render the tree directive work. Mainly the compile function
When all the compile function called?
When is the $compile function cached in the varibale compiledContents (is this the link function?), and when is it appends? Why it is not append always?
--
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
},
The Ng site has some great documentation (some of the best around in my opinion). The overview of the Startup and Runtime loops are very helpful:
http://docs.angularjs.org/guide/concepts
At a high level, when Ng first starts it compiles the DOM starting at where ng-app is located (treated like just another directive by Ng). This means it goes through the elements and looks directives and expressions it needs to link up to the $rootScope (the root of all scopes that are part of the prototypical inheritance chain setup by the compiling/linking process). If it is a directive, the compile process is done on it as well. The compiling process takes all of the Ng directives it finds in the HTML and prioritizes them based on there assigned priority or assumes the priority is zero. When it has them all ordered it executes the compile function for the directive which returns the link function. In the above example there are two show link functions which I will annotate below along with other notes linking it to this explanation. the link function also is given the HTML that was in the element the directive was a attribute, class, or element on in the form of the transclude object.
The link functions are executed which links the scope and the directive along with producing a view. This may include the HTML/transclude so it can be added where the directive ng-transclude is in the template of the directive (which will have the same process applied to it with it's template being the transclude).
So here are my notes for the slightly corrected custom directive above:
module.directive("tree", function($compile) {
//Here is the Directive Definition Object being returned
//which is one of the two options for creating a custom directive
//http://docs.angularjs.org/guide/directive
return {
restrict: "E",
//We are stating here the HTML in the element the directive is applied to is going to be given to
//the template with a ng-transclude directive to be compiled when processing the directive
transclude: true,
scope: {family: '='},
template:
'<ul>' +
//Here we have one of the ng-transclude directives that will be give the HTML in the
//element the directive is applied to
'<li ng-transclude></li>' +
'<li ng-repeat="child in family.children">' +
//Here is another ng-transclude directive which will be given the same transclude HTML as
//above instance
//Notice that there is also another directive, 'tree', which is same type of directive this
//template belongs to. So the directive in the template will handle the ng-transclude
//applied to the div as the transclude for the recursive compile call to the tree
//directive. The recursion will end when the ng-repeat above has no children to
//walkthrough. In other words, when we hit a leaf.
'<tree family="child"><div ng-transclude></div></tree>' +
'</li>' +
'</ul>',
compile: function(tElement, tAttr, transclude) {
//We are removing the contents/innerHTML from the element we are going to be applying the
//directive to and saving it to adding it below to the $compile call as the template
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
//Get the link function with the contents frome top level template with
//the transclude
compiledContents = $compile(contents, transclude);
}
//Call the link function to link the given scope and
//a Clone Attach Function, http://docs.angularjs.org/api/ng.$compile :
// "Calling the linking function returns the element of the template.
// It is either the original element passed in,
// or the clone of the element if the cloneAttachFn is provided."
compiledContents(scope, function(clone, scope) {
//Appending the cloned template to the instance element, "iElement",
//on which the directive is to used.
iElement.append(clone);
});
};
}
};
});
Whole thing working:
http://jsfiddle.net/DsvX6/7/
I've a question about a directive I want to write. Here is the jsfiddle
This is the HTML
<div ng-controller="TestCtrl">
<mydir base="{{absolute}}">
<div>{{path1}}</div>
<div>{{path2}}</div>
</mydir>
</div>
The directive, called 'mydir', will need the value of the base attribute and all the path values (there can be any number of paths defined). I don't want to inject values from the controller directly into the directive scope (they need to be independent). I have 3 questions about this (checkout the jsfiddle too!)
1) although the code works as it is now, there is an error: "TypeError: Object # has no method 'setAttribute'". Any suggestions what is wrong?
2) in the directive scope.base is 'undefined' instead of '/base/'. Can this be fixed.
3) How can I make a list of all the path values in the directive ?
You need to set an isolate scope that declares the base attribute for this directive:
scope: {
base: "#"
}
This solves (2).
Problem (1) is caused from the replace: true in cooperation with the scope above, because it creates a comment node to contain the repeated <div>s.
For (3), add paths: "=" to scope and, of ocurse, pass them from the parent controller as an array.
See forked fiddle: http://jsfiddle.net/swYAZ/1/
You're creating an new scope in your directive by setting scope: true what you want to do is:
app.directive('mydir', function ($compile, $rootScope) {
return {
replace: true,
restrict: 'E',
template: '<div ng-repeat="p in paths"> {{base}}{{p}}/{{$index}}</div>',
scope: {
base : '='
},
link: function (scope, iElement, iAttrs) {
scope.paths = ['p1', 'p2'] ;
}
}
});
This will setup a bi-directional binding between your directive's local scope and the parent's scope. If the value changes in the parent controller it will change in the directive as well.
http://jsfiddle.net/4LAjf/8/
If you want to make a robust directive to be able to replace the data to its content (3)
You can pass a reference as an attribute and display the data accordingly.
See this article, look for wrapper widget.