AngularJS: Using an attribute set in one directive, within another directive - angularjs

Scenario
Lets say I have two directives first and second where the directives are used on the same DOM element as such:
HTML
<div first second></div>
Javascript
//first directive
.directive('first', function(){
return {
restrict: 'A',
priority: 1,
link: function(scope, element, attrs){
element.attr('someAttr', 1234);
element.html('I have the attribute value: ' + element.attr('someAttr'));
}
};
})
// second directive
.directive('second', function(){
return {
restrict: 'A',
priority: 0,
controller: function($scope, $element, $attrs){
// the elements attribute `someAttr` is undefined here
}
};
})
The first directive adds an attribute (someAttr) to the element where it is being used.
Problem
What I want to achieve is that access/use the set value to someAttr (set in first directive) in the second directive's link/controller function. But for the moment, I haven't been successful in doing so. (Check this fiddle out)
Note: I've also set the priority of the first directive to a value higher than that of second, but still when you log the attributes of the element where the directive is used, there is no someAttr attribute in the set.
Also note that if how I want to achieve the communication between directives is not appropriate, then what is the right way to do so? Any help would be appreciated.

You cannot cannot detect changes in attributes set to the DOM element directly (at least not in angularjs way).
Try using a property in the first directive controller and then 'require' 'first' in 'second' to access 'second' properties.
Check the information about the 'requires' property when defining a directive to see how it works.
UPDATE: Check this plunkr:
http://plnkr.co/edit/bEbO8LvCIUd0NZPFqMnv?p=preview
app.directive('first', function(){
return {
restrict: 'A',
link: function(scope, element, attrs){
element.html('I (first) have the scope value: ' + scope.data.attribute);
},
controller: function($scope) {
this.data = $scope.data = {
attribute: 1234
};
}
};
});
// second directive
app.directive('second', function(){
return {
restrict: 'A',
require: 'first',
priority: 1,
link: function($scope, element, attrs, firstController){
element.html(element.html() + ' and I (second) have access to that value too: ' + firstController.data.attribute);
//you can $scope.$watch firstController.data changes here too
}
};
});

Related

Get element scope from attribute directive

I'm trying to extend functionality of any directive by simply attaching an attribute directive, but I'm having trouble getting the scope of the element on which the attribute is defined.
For example, I have this template:
<div class="flex-item-grow flex-item flex-column report-area">
<sv-report sv-reloadable id="reportId"></sv-report>
</div>
Here, sv-reloadable has some implicit understanding of sv-report, but sv-report has no idea about sv-reloadable.
I've defined sv-reloadable as:
angular
.module( 'sv-reloadable', [
'sv.services',
])
.directive('svReloadable', function(reportServices, $timeout) {
return {
restrict: 'A',
controller: function($scope, $timeout) {
$scope.$on('parameter-changed', function(evt, payload) {
evt.stopPropagation();
$scope.viewModel = getNewViewModel(payload);/* hit the server to retrieve new data */
});
}
};
});
Now, $scope in sv-reloadable is the parent scope of sv-report. I'm wanting sv-reloadable to be able to attach a listener to sv-report's scope, and swap out properties of that scope. I understand that it's possible to grab the sibling scopes, but that causes problems when trying to figure out exactly which element it's attached to.
I attempted the following:
link: function(scope, element, attrs) {
ele = element;
var actualScopyThingy = element.scope();
},
Which I had assumed would give me the scope of the element the attribute was defined on, but alas, it still returns the parent scope of the element.
If it's important, sv-report is defined as the following, but I'd like to be able to keep it the same (since sv-reloadable is going to be attached to many different elements, all of which must have viewModel defined on their scope)
return {
restrict: 'E',
replace: true,
templateUrl: 'sv-report/sv-report.tpl.html',
scope: {
id: '=',
reportParameters: '='
},
controller: function ($scope, svAnalytics) {
/* unrelated code here */
},
link: function(scope, element, attrs) {
initialLoadReport(scope);
}
};
After a bit of digging around, isolateScope() is what I was after (rather than scope()). sv-reloadable's directive becomes:
return {
restrict: 'A',
link: function(scope, element, attrs) {
var elementScope = element.isolateScope();
elementScope.$on('parameter-changed', function(evt, payload) {
...
});
}
};

Dynamically Create directive

i've created a directive dynamically and then i passed a data to this directive but this directive not rendered and i can access passed data into link function, and here my snippet code:
var table = $compile("<user-priv data=object ><user-priv>")($scope);
angular.element(document).find('#privModal').find('.modal-body').append(table);
angular.element(document).find('#privModal').modal('show')
And here is Directive code
.directive('userPriv', [function() {
return {
restrict: 'A',
scope: {
data: '=?'
},
templateUrl: 'file/angular/templates/privList.html',
link: function(scope, iElement, iAttrs) {
console.warn(scope.data);
},
controller: function($scope) {
console.log('test');
}
};
}])
Not sure what you succeeded or not from your post, but one thing looks off:
restrict: 'A' should be restrict: 'E' since you're using the directive as an Element, not as an Attribute.
Parameter passed to directive are always shown as html attribute: in your code you are missing some colons "".
Change your $compile line to:
var table = $compile("<user-priv data="object" ><user-priv>")($scope);

angularjs - is it not possible to add ng- attributes on a directive?

What I would like to be able to do is "wrap" the behavior of an ng-hide for a "permissions" directive... so I can do the following
Hide me
All is fine if I decide to simply "remove" the element from the dom; however, if I try to add an ng-hide and then recompile the element. Unfortunately, this causes an infinite loop
angular.module('my.permissions', []).
directive 'permit', ($compile) ->
priority: 1500
terminal: true
link: (scope, element, attrs) ->
element.attr 'ng-hide', 'true' # ultimately set based on the user's permissions
$compile(element)(scope)
OR
angular.module('my.permissions', []).directive('permit', function($compile) {
return {
priority: 1500,
terminal: true,
link: function(scope, element, attrs) {
element.attr('ng-hide', 'true'); // ultimately set based on the user's permissions
return $compile(element)(scope);
}
};
});
I've tried it without the priority or terminal to no avail. I've tried numerous other permutations (including removing the 'permit' attribute to prevent it from continually recompiling, but what it seems to come down to is this: there doesn't seem to be a way to modify an element's attributes and recompile inline through a directive.
I'm sure there's something I'm missing.
This solution assumes that you want to watch the changes of the permit attribute if it changes and hide the element as if it was using the ng-hide directive. One way to do this is to watch the permit attribute changes and then supply the appropriate logic if you need to hide or show the element. In order to hide and show the element, you can replicate how angular does it in the ng-hide directive in their source code.
directive('permit', ['$animate', function($animate) {
return {
restrict: 'A',
multiElement: true,
link: function(scope, element, attr) {
scope.$watch(attr.permit, function (value){
// do your logic here
var condition = true;
// this variable here should be manipulated in order to hide=true or show=false the element.
// You can use the value parameter as the value passed in the permit directive to determine
// if you want to hide the element or not.
$animate[condition ? 'addClass' : 'removeClass'](element, 'ng-hide');
// if you don't want to add any animation, you can simply remove the animation service
// and do this instead:
// element[condition? 'addClass': 'removeClass']('ng-hide');
});
}
};
}]);
angular.module('my.permissions', []).directive('permit', function($compile) {
return {
priority: 1500,
terminal: true,
link: function(scope, element, attrs) {
scope.$watch(function(){
var method = scope.$eval(attrs.permit) ? 'show' : 'hide';
element[method]();
});
}
};
});
I'm using this directive. This works like ng-if but it checks for permissions.
appModule.directive("ifPermission", ['$animate', function ($animate) {
return {
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
$$tlb: true,
link: function ($scope, $element, $attr, ctrl, $transclude) {
var block, childScope;
var requiredPermission = eval($attr.ifPermission);
// i'm using global object you can use factory or provider
if (window.currentUserPermissions.indexOf(requiredPermission) != -1) {
childScope = $scope.$new();
$transclude(childScope, function (clone) {
clone[clone.length++] = document.createComment(' end ifPermission: ' + $attr.ngIf + ' ');
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when it's template arrives.
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
}
};
}]);
usage:
<div if-permission="requiredPermission">Authorized content</div>

AngularJS: link function is not being passed a Controller

I am trying to create a directive:
return {
restrict: 'A', // Attribute Directive
ngModel: '^ngModel',
scope: {
'ngModel': '='
},
link: function ($scope: ng.IScope, element, attrs, ctrl) {
var datePickerOptions = {
autoclose: true,
format: attrs.aceDatepickerFormat,
weekStart: attrs.aceDatepickerWeekstart
};
// Attach the datepicker events (must have Bootstrap.DatePicker referenced).
element.datepicker(datePickerOptions).next().on('click', function () {
$(this).prev().focus();
});
element.click(() => {
ctrl.$setViewValue(new Date());
});
}
};
In this example, when the click event occurs on the element, I wish to use ctrl.$setViewValue to the current date (this is a test).
When the link function is called, scope, element and atts are all populated correctly, however the ctrl is null.
The element is with a div with ng-controller set.
<div ng-controller="Controllers.FormElementsController">
<input class="form-control date-picker" id="id-date-picker-1" type="text"
ng-model="DatePickerValue"
ace-datepicker-weekstart="1"
ace-datepicker-format="dd-mm-yyyy"
ace-datepicker="" />
</div>
Why is no controller being passed here?
You have to use require to pull in the controller (ngModelController in your case):
return {
restrict: 'A', // Attribute Directive
require: '^ngModel',
You had it set to ngModel as the property name.
From the docs:
The myPane directive has a require option with value ^myTabs. When a
directive uses this option, $compile will throw an error unless the
specified controller is found. The ^ prefix means that this directive
searches for the controller on its parents (without the ^ prefix, the
directive would look for the controller on just its own element).

angular - Listening on transcluded change

I have a simple directive
angular.module('myApp')
.directive('myDirective', function () {
return {
template: '<p ng-transclude></p>',
restrict: 'A',
transclude: true,
link: function postLink(scope, element, attrs) {
}
}
}
);
I am trying to run code each time the transclusion content changes and the directive is rendered - I need the transcluded content.
Example algorithm I would like to run in this case is:
count words of transcluded content.
I have tried scope.$watch in multiple forms but to no avail.
We can use the jqlite included within Angular inside a watch expression function to accomplish this. Below is code that watches the length of the transcluded element using jqLite (element.text().length). The watch fires whenever the length of the element that this directive is attached to changes.
And the new length is passed in as newValue to the second function within the watch (since we return it from the first watch function).
myApp.directive('myDirective', function () {
return {
template: '<p ng-transclude></p>',
restrict: 'A',
transclude: true,
replace: true,
link: function (scope, element, attrs) {
scope.$watch(function () {
return element.text().length;
},
function (newValue, oldValue) {
console.log('New Length ', newValue);
});
}
}
});
I've got a working jsfiddle here:
http://jsfiddle.net/2erbF/6/
This addresses the word/letter count scenario. But you could write a test on the element.text() itself if you needed it to fire on any changes- not just a length change.

Resources