I have created custom directive and applied it two times on the same html page.
In the controller of the directive I have function that sets value of some TEMP variable. When I use let's say first directive and inside of it set value of this TEMP variable and after that when I use the second directive value of TEMP variable is not available.
How can I achieve this ?
I would recommend having a service or controller (depending on whether a controller is applicable in this case) that sits above the directives. It can hold the base copy of the variable, and be read from or written to.
For example, in your directive:
localTemp = tempService.temp;
// or scope.temp if pulling scope from a controller like link: function (scope, elem, attr) {...}
...
tempService.temp = localTemp;
You may have to put this second line inside a $timeout(...) if you have this variable directly visible on your page in order for the change to be picked up by the Angular digest, propagated and displayed in your UI.
Related
Within a view I'm using ng-repeat inside a directive to loop over an array of objects from my controller. During this phase the objects inside the array suffer value changes. When I change the view and create a new instance of the same directive, I want to loop over the array of objects yet again but this time loop over the updated array of objects.
If I modify the objects in the first view and pass the same array of objects in the second view, the directive uses the initial array, not the updated one. What I tried is watching the array for changes, it sees the changes but still during the second instance of the directive it uses the initial array.
Can someone explain whats going on and how could I solve this ?
You can have a simple parent child relationship directive, in which directive have access to scope of controller o the view in which it's added. so you can have ng-repeat in directive & array in scope variable of controller. Then automatically the values will be sync by two way data binding. You can follow following plunk I've created.
It depends upon how are you sharing data between controller & directive. Two data binding helps make it sync else you've to handle it over event or using $watch in directive (if you're using jquery code inside directive).
https://plnkr.co/edit/m47TPgcX7lY8nL1LswCv?p=preview
app.directive('helloWorld', function ($compile, CarCompany) {
return {
restrict: 'E',
template: '<div>\
<h2>HelloWorld directive</h2>\
<p ng-repeat="car in carc">{{car.name}}</p>\
<h3>Liste des marques:</h3>\
</div>',
link: function (scope, elem, attrs) {
var ul = document.createElement('ul');
var lis = '';
angular.forEach(CarCompany, function(item) {
lis += '<li>' + item.name + '</li>';
});
ul.innerHTML = lis;
elem.append(ul);
$compile(ul)(scope);
},
};
});
This's simple directive showing shared scope between controller & directive.
https://www.infragistics.com/community/blogs/dhananjay_kumar/archive/2015/06/11/understanding-scopes-in-angularjs-custom-directives.aspx
So I need to be able to call a function of a directives controller, have it wrap an existing child directive in a new one while maintaining the now wrapped directive's state (both it's controller and all possible children) and otherwise modify the parent directive's template. This process also has to able to reversed.
Quick and super dirty fiddle to show the idea
So the DOM starts as:
<split>
<innerDirective></innerDirective>
</split>
When the split directive's controller's function is called, this should happen:
<split> // same outer parent
<split> // new directive
<innerDirective></innerDirective>// SAME innerDirective instance
</split>
<split>// new directive
<innerDirective></innerDirective> //New innderDirective instance
</split>
</split>
I originally used the .html function to set the split directives content, and used $compile service to reconstruct the DOM. This of course destroys the original innerDirective. Since it may be deep, it is not feasible to store it's data in the parent split and retrieve with a link function (although I tried).
I also tried using jquery's .wrap and .unwrap functions, which works, except the added tags aren't compiled as directives, so not really.
Is there a proper way to do this, or a way to compile just the outer tag added with the .wrap, and update the scope of the innerDirective to the new parent?
Thanks for any help and advice you may have!
Edit:
I have a controller along this lies, lets say set to control the split 'E' directive:
function SplitCtrl($scope, $compile){
//stuff
//First way, redefine html contents and recompile
this.split1 = function(){
//element has been linked to the directive's element
$scope.element.html('<split></split><split></split>');
$compile($scope.element.contents())($scope);
};
//Second way, jquery.wrap
this.split2 = function(){
$($scope.element).children('innerDirective').wrap('<split />');
// then I need to instantiate the new split
// without reinstantiating now wrapped directive.
// Adding second split is fine in this case, I can do that.
};
};
The template is:
<innderDirective></innerDirective>
I created a custom <select> dropdown element using a directive, which has a selecting attribute that is used in the directive to pull the correct data from the controller to populate the list of options. I'd like to know if this is an appropriate use of AngularJS's different pieces. I'm new to Angular, but this is how it occurred to me to accomplish this task.
In the HTML partial for a page needing my version of select, this is all that needs to be written:
<sliding-select selecting="areas"></sliding-select>
In the controller there is a corresponding variable on the main object called areas which contains within it an array called options, whose members should be the options of the area select.
And here is the part of the directive that takes that data and creates an option element for each member of the areas array :
.directive('slidingSelect', function($timeout) {
return {
restrict: 'E',
compile: function(element, attrs) {
var selecting = attrs.selecting;
// ng-repeated options come from selecting attr
var dropDownList = $('<div class="sliding-select-list"></div>');
dropDownList.append('<div class="sliding-select-option" ng-repeat="item'+
' in home.'+selecting+'.options">{{item.name}}</div>');
element.append(dropDownList);
}
};
I'm just a bit worried that it seems hacky to create an element whose ng-repeat phrase uses the variable name by creating a string from it. Maybe there's a totally different approach to this problem that's preferred by the Angular community?
According to the documentation a template can be a function which takes two parameters, an element and attributes and returns a string value representing the template. It replaces the current element with the contents of the HTML. The replacement process migrates all of the attributes and classes from the old element to the new one.
The compile function deals with transforming the template DOM. It takes three parameters, an element, attributes and transclude function. The transclude parameter has been deprecated. It returns a link function.
It appears that a template and a compile functions are very similar and can achieve the same goal. The template function defines a template and compile function modifies the template DOM. However, it can be done in the template function itself. I can't see why modify the template DOM outside the template function. And vice-versa if the DOM can be modified in the compile function then what's the need for a template function?
The compilation function can be used to change the DOM before the resulting template function is bound to the scope.
Consider the following example:
<div my-directive></div>
You can use the compile function to change the template DOM like this:
app.directive('myDirective', function(){
return {
// Compile function acts on template DOM
// This happens before it is bound to the scope, so that is why no scope
// is injected
compile: function(tElem, tAttrs){
// This will change the markup before it is passed to the link function
// and the "another-directive" directive will also be processed by Angular
tElem.append('<div another-directive></div>');
// Link function acts on instance, not on template and is passed the scope
// to generate a dynamic view
return function(scope, iElem, iAttrs){
// When trying to add the same markup here, Angular will no longer
// process the "another-directive" directive since the compilation is
// already done and we're merely linking with the scope here
iElem.append('<div another-directive></div>');
}
}
}
});
So you can use the compile function to change the template DOM to whatever you like if your directive requires it.
In most cases tElem and iElem will be the same DOM element, but sometimes it can be different if a directive clones the template to stamp out multiple copies (cf. ngRepeat).
Behind the scenes, Angular uses a 2-way rendering process (compile + link) to stamp out copies of a compiled piece of DOM, to prevent Angular from having to process (= parse directives) the same DOM over and over again for each instance in case the directive stamps out multiple clones resulting in much better performance.
Hope that helps!
ADDED AFTER COMMENT:
The difference between a template and compile function:
Template function
{
template: function(tElem, tAttrs){
// Generate string content that will be used by the template
// function to replace the innerHTML with or replace the
// complete markup with in case of 'replace:true'
return 'string to use as template';
}
}
Compile function
{
compile: function(tElem, tAttrs){
// Manipulate DOM of the element yourself
// and return linking function
return linkFn(){};
}
}
The template function is called before the compile function is called.
Although they can perform almost identical stuff and share the same 'signature', the key difference is that the return value of the template function will replace the content of the directive (or the complete directive markup if replace: true), while a compile function is expected to change the DOM programmatically and return a link function (or object with pre and post link function).
In that sense you can think of the template function as some kind of convenience function for not having to use the compile function if you simply need to replace the content with a string value.
Hope that helps!
One of the best uses of the template function is to conditionally generate a template. This allows you to automate the creation of a template based on an attribute or any other condition.
I have seen some very large templates that use ng-if to hide sections of the template. But instead of placing everything into the template and using ng-if, which can cause excessive binding, you can remove sections of the DOM from the output of the template function that will never be used.
Let's say you have a directive that will include either sub-directive item-first or item-second. And the sub-directive will not ever change for the life of the outer directive. You can adjust the output of the template, prior to the compile function being called.
<my-item data-type="first"></my-item>
<my-item data-type="second"></my-item>
And the template string for these would be:
<div>
<item-first></item-first>
</div>
and
<div>
<item-second></item-second>
</div>
I agree that this is an extreme simplification, But I have some very complicated directives and the outer directive needs to display one of, about, 20 different inner directives based on a type. Instead of using transclude, I can set the type on the outer directive and have the template function generate the correct template with the correct inner directive.
That correctly formatted template string is then passed on to the compile function, etc.
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.