Basically, I'm trying to create a remote file browser. When the user clicks a folder, the directive should reload with the new parameter (the path).
Please see my code below.
NwkApp.directive('wip', function ($parse, $http, $rootScope, $compile) {
var WipAPI = 'example.com';
if($rootScope.path === undefined) {
$rootScope.path = new Array();
WipAPI += "?Path="+$rootScope.path.join('/');
}
return {
restrict: 'E',//This makes it relevant to the Element. Other values could be A for attr, or M for comment
replace: true,
templateUrl: WipAPI,
scope : {
data: '='//binds whatever is in data, a reference to an var, obj or array.
},
link: function (scope, element, attrs) {
scope.wipBrowse = function(subpath) {
$rootScope.path.push(subpath);
element.parent().html('<wip></wip>');
}//End wipBrowse
}//End link folder
}//End return
});
Basically, the template HTML contains multiple ng-clicks which do trigger scope.wipBrowse once. As soon as I replace the parent html with (in the hope I'll trigger the directive again), nothing happens.
Is there an easy way to trigger the directive to run again?
i'm not sure if this will help you but the click events are not firing because your html is replaced but not compiled. I made a slightly modified version of your directive:
app.directive('wip', function($compile, $rootScope) {
return {
restrict: 'E', //This makes it relevant to the Element. Other values could be A for attr, or M for comment
replace: true,
templateUrl: 'test.html',
scope: {
data: '=' //binds whatever is in data, a reference to an var, obj or array.
},
link: function(scope, element, attrs) {
scope.wipBrowse = function(subpath) {
alert('alert');
$rootScope.path.push(subpath);
element.replaceWith($compile('<wip></wip>')(scope)[0]);
} //End wipBrowse
} //End link folder
} //End return
});
http://plnkr.co/edit/OyN51DDJzFMhtM4DwJ3K?p=preview
Related
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) {
...
});
}
};
i want to pass value from directive link to my html file. I am not using scope.
app.directive('isolate', function () {
return {
restrict: 'EA',
templateUrl: 'template.html',
link: function (element, attrs) {
var This = this;
This.testlinkdata = 'data';
}
};
});
in the above code i want to access testlinkdata in html
<h4>Test Link: {{testlinkdata}}</h4>
if i do so i am not getting any value
Here is my plunker https://plnkr.co/edit/WtUfji1utl7AvaGDxcec?p=preview
how can i access local variable without scope?
Its a simple directive:
app.directive('ngFoo', function($parse){
var controller = ['$scope', function ngNestCtrl($scope) {
$scope.getCanShow = function() {
console.log('show');
return true;
};
}];
var fnPostLink = function(scope, element, attrs) {
console.log('postlink');
};
var fnPreLink = function(scope, element, attrs) {
console.log('prelink');
};
var api = {
template: '<div ng-if="getCanShow()">foo</div>',
link: {
post: fnPostLink,
pre: fnPreLink
},
restrict: 'E',
controller:controller
};
return api;
});
My goal was to find when "show" gets output to console. At this moment I figure it happens after linking (pre & post).
This makes sense. Since the template is rendered after those phases. (Correct me if I am wrong).
But then again, why would the template be rendered twice?
http://plnkr.co/edit/JNhON2lY9El00dzdL39J?p=preview
Angular has multiple digest cycles and you're seeing two of them. This is totally normal and perfectly ok.
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>
I created a very simple directive which displays a key/value pair. I would like to be able to automatically hide the element if the transcluded content is empty (either zero length or just whitespace).
I cannot figure out how to access the content that gets transcluded from within a directive.
app.directive('pair', function($compile) {
return {
replace: true,
restrict: 'E',
scope: {
label: '#'
},
transclude: true,
template: "<div><span>{{label}}</span><span ng-transclude></span></div>"
}
});
For example, I would like the following element to be displayed.
<pair label="My Label">Hi there</pair>
But the next two elements should be hidden because they don't contain any text content.
<pair label="My Label"></pair>
<pair label="My Label"><i></i></pair>
I am new to Angular so there may be a great way handle this sort of thing out of the box. Any help is appreciated.
Here's an approach using ng-show on the template and within compile transcludeFn checking if transcluded html has text length.
If no text length ng-show is set to hide
app.directive('pair', function($timeout) {
return {
replace: true,
restrict: 'E',
scope: {
label: '#'
},
transclude: true,
template: "<div ng-show='1'><span>{{label}} </span><span ng-transclude></span></div>",
compile: function(elem, attrs, transcludeFn) {
transcludeFn(elem, function(clone) {
/* clone is element containing html that will be transcludded*/
var show=clone.text().length?'1':'0'
attrs.ngShow=show;
});
}
}
});
Plunker demo
Maybe a bit late but you can also consider using the CSS Pseudo class :empty.
So, this will work (IE9+)
.trancluded-item:empty {
display: none;
}
The element will still be registered in the dom but will be empty and invisible.
The previously provided answers were helpful but didn't solve my situation perfectly, so I came up with a different solution by creating a separate directive.
Create an attribute-based directive (i.e. restrict: 'A') that simply checks to see if there is any text on all the element's child nodes.
function hideEmpty() {
return {
restrict: 'A',
link: function (scope, element, attr) {
let hasText = false;
// Only checks 1 level deep; can be optimized
element.children().forEach((child) => {
hasText = hasText || !!child.text().trim().length;
});
if (!hasText) {
element.attr('style', 'display: none;');
}
}
};
}
angular
.module('directives.hideEmpty', [])
.directive('hideEmpty', hideEmpty);
If you only want to check the main element:
link: function (scope, element, attr) {
if (!element.text().trim().length) {
element.attr('style', 'display: none;');
}
}
To solve my problem, all I needed was to check if there were any child nodes:
link: function (scope, element, attr) {
if (!element.children().length) {
element.attr('style', 'display: none;');
}
}
YMMV
If you don't want to use ng-show every time, you can create a directive to do it automatically:
.directive('hideEmpty', ['$timeout', function($timeout) {
return {
restrict: 'A',
link: {
post: function (scope, elem, attrs) {
$timeout(function() {
if (!elem.html().trim().length) {
elem.hide();
}
});
}
}
};
}]);
Then you can apply it on any element. In your case it would be:
<span hide-empty>{{label}}</span>
I am not terribly familiar with transclude so not sure if it helps or not.
but one way to check for empty contents inside the directive code is to use iElement.text() or iElement.context object and then hide it.
I did it like this, using controllerAs.
/* inside directive */
controllerAs: "my",
controller: function ($scope, $element, $attrs, $transclude) {
//whatever controller does
},
compile: function(elem, attrs, transcludeFn) {
var self = this;
transcludeFn(elem, function(clone) {
/* clone is element containing html that will be transcluded*/
var showTransclude = clone.text().trim().length ? true : false;
/* I set a property on my controller's prototype indicating whether or not to show the div that is ng-transclude in my template */
self.controller.prototype.showTransclude = showTransclude;
});
}
/* inside template */
<div ng-if="my.showTransclude" ng-transclude class="tilegroup-header-trans"></div>