Issue on AngularJS directive link and ngHref - angularjs

I will try to shorten my problem description as much as possible here.
I have a directive which looks as following
scope: false
restrict: "A"
link: function (scope, element, attributes) {
...
}
and the input parameter element looks as following
<div class="tab-container" my-directive>
<div ng-repeat="tab in $ctrl.tabs" class="tab">
<a ng-href="{{tab.name}}">
</div>
</div>
ng-repeat has been "resolved", unsure if that the correct term, however ng-href has not been resolved, that is, it hasn't turned {{tab.name}} to the value I need.
I wonder how I can tell this directive to run link, after ng-href has been resolved first.
I read something about require property, but it seems to handle controller only.

I wonder how I can tell this directive to run link, after ng-href has
been resolved first.
You can send $broadcast event from your controller to directive.
$rootScope.$broadcast('onHrefReady');
and in your directive:
link : function (scope, element, attributes) {
scope.$on("onHrefReady", function (event, args) {
// do your stuff
});
}
link is called after rendering, so you can call link content after got notification from controller

Related

Run 'ng-click' inside a directive's isolated scope

Thanks in advance for taking the time to look into this question
I have serverside generated code that renders a directive wrapped around pre-rendered content.
<serverside-demo color="blue">
<p><strong>Content from Server, wrapped in a directive.</strong></p>
<p>I want this color to show: <span ng-style="{color: color}">{{color}}</span></p>
<button ng-click="onClickButtonInDirective()">Click Me</button>
</serverside-demo>
This means that 1.) the directive tag, 2.) the content inside the directive tag, 3.)the ng-click and 4.) The curly braces syntax are all generated by the server.
I want AngularJs to pick up the generated code, recompile the template and deal with the scope.
The problem is that I am having trouble getting it working. I understand that because the ng-click is inside the controller block, it is picked up not by the directive isolated scope, but the parent controllers. Instead I want the opposite... to pick up the onClickButtonInDirective scope function inside the serversideDemo link
I have created a jsfiddle best explaining my problem, which aims to clearly demonstrate the working "traditional" way of loading the template separately (which works) comparing it to the server-side way.
https://jsfiddle.net/stevewbrown/beLccjd2/3/
What is the best way to do this?
Thank you!
There are two major problem in your code
1- directive name and dom element not matched, - missing in dom element
app.directive('serverSideDemo', function() {
use <server-side-demo color="blue"> instead of <serverside-demo color="blue">
2- you need to compile the html code of server-side-demo dom with directive scope in link function
$compile(element.contents())(scope);
Working jsfiddle
Use templateUrl instead of template to fetch the content of directive from server:
app.directive('serverSideDemo', function() {
return {
restrict: 'AE',
scope: {
color: '='
},
templateUrl: 'link/that/returns/html/content',
link: function(scope, element, attrs) {
scope.onClickButtonInDirective = function() {
console.log('You clicked the button in the serverside demo')
scope.color = scope.color === 'blue' ? 'red' : 'blue';
}
}
};
});
Have a look at angular docs for more details

AngularJS directive that has ng-repeat and works after being re-added to DOM

In AngularJS, I need to create a directive that runs ng-repeat. I need to bind that repeat data to the element. Also, once the element has been removed from the DOM and re-added, I need the ng-click directive to work. How can I setup the html for using ng-repeat with data binding. Most importantly, these elements will be removed and re-added and their ng-click event still needs to work.
Here is the inline that I had
<li style="" class="news-item text-left" ng-repeat="item in News.GeneralNews">
<strong>{{item.DateValue | date: "MMM dd yyyy"}}</strong>
{{item.Preview}}
<a class="FakeClickable" ng-click="UpdateNews('General',item.index)"
data-target="#GeneralModal" data-toggle="modal">Read more...</a>
</li>
I don't understand how to make this work. I attempted to copy directive examples, but this seems more complex.
Directive: so far
app.directive('compNews', ['$compile',function ($compile) {
return {
restrict: 'AEC',
link: function (scope, element, attrs)
{
element.append($compile(scope.$eval(attrs.actionBtn))(scope));
},
templateUrl: '../pages/TemplateUrls/compNews.html'
}
}]);
My understanding is that I can place this element inside my HTML markup as <comp-News></comp-News>

How to get the parent(/parent/parent/..etc) controller from inside a directive?

I have 3 directives, which structure might look something like this:
<my-component>
<my-items>
<my-item="item" ng-repeat="item in MyItems.items">
{{ item.name }}
<!-- item.children -->
<my-items>
<my-item="item" ng-repeat="item in MyItems.items" />
</my-items>
</my-item>
<my-items>
</my-component>
where the depth of my-items can be infinite (in theory).
Now I want to have the my-component hold the (ONE!) item that the user selects and add a conditional selected class, so in (~pseudo) code which will look something like this:
<my-item="item" ng-click="myComponent.selected = item"
ng-class="{ selected: myComponent.selected == item }" />
How can I know in here what the selected item is of the my-component?
I have tried to do this in myItems directive:
// itemsComponentController is the controller of my-component
require: "^^itemsComponentController";
But angular here throws the error it cannot find such a controller. What am I doing wrong here with the require? Does it not find controllers that are not its direct parents?
I have it working on emitting the 'item-selected' event, which is handled in the controller by doing the scope.$on("item-selected"), but then I still cannot know the myComponent.selected in <my-item>
See this jsfiddle for the (minimalistic?) typescript version of this problem.
I found the issue!
I don't have to require the controller's name, but instead require the directive's name. Then the controller of that directive will be passed as variable in the link function:
// Require the name of the (parent) directive instead of its controller name
require: "^^myComponent";
link: function(scope, elem, attrs, ctrl) {
// ctrl is the controller of <my-component>
// so now I can do ctrl.itemSelected()
}
The working jsfiddle

Is there an alternative to using a directive to create template code?

I had HTML that was duplicated on many screens so I created a directive like this:
app.directive('adminRetrieveButton', ['stateService', function (stateService) {
return {
restrict: 'AE',
template: "<button id='retrieveButton'\
ng-disabled='!home.forms.grid.$pristine'\
ng-click='ctrl.retrieve()' >Retrieve\
<span class='fa fa-fw mlr75'\
ng-class='{\"fa-spin fa-spinner\": stateService.action[Network.Retrieve], \"fa-download\": !stateService.action[Network.Retrieve] }' >\
</span>\
</button>",
link: function (scope, element, attrs) {
scope.stateService = stateService;
}
};
}]);
The directive and other similar ones has no functionality other than to create some template type of code. Now rather than having to code in 8 lines of HTML in each page I just have one line:
<admin-retrieve-button></admin-retrieve-button>
Is there another alternative to this that would make it even simpler without my needing to create a directive?
yes you dont need to create directive only for template you can simply use ng-include
Directives are a powerful tool for working with and modifying the DOM, If you dont manipulating with DOM you dont need directive
There are alternatives, but your method is the best. The way you have it is the correct Angular Way; if you have a standard control, make it a directive.
If you like, you can use the templateUrl option to include the HTML in a separate file instead of inline in the directive configuration:
app.directive('adminRetrieveButton', ['stateService', function (stateService) {
return {
restrict: 'AE',
templateUrl: "adminButton.html",
link: function (scope, element, attrs) {
scope.stateService = stateService;
}
};
}]);
...and in adminButton.html:
<button id="retrieveButton"
ng-disabled="!home.forms.grid.$pristine"
ng-click="ctrl.retrieve()" >Retrieve
<span class="fa fa-fw mlr75"
ng-class="{'fa-spin fa-spinner': stateService.action[Network.Retrieve],
'fa-download': !stateService.action[Network.Retrieve]}">
</span>
</button>
This directive can access its parent scope because it has not been set to isolate scope; if you included a "scope" option on the directive call you'd need to pass in the home and ctrl variables.
You can use ng-include to do this, but your view code will look less semantic and injecting stateService will be a bit more complicated.

Is it possible to conditionally apply transclution to directive?

Is it possible to decide whether to apply transclusion to an element based on a scope variable ?
For example ( Stupid simplified reduced example of what i'm trying to achieve )
app.directive('myHighlight', function () {
return {
transclude : true,
template : "<div style='border:1px solid red'><span ng-transclude></span></div>"
}
});
app.directive('myDirective', function () {
return {
template : "<span>some text</span>",
link : function (scope,element,attr) {
if ( 'shouldHighlight' in attr) {
// wrap this directive with my-highlight
}
}
}
});
And then in the html
<span my-directive></span>
<span my-directive should-highlight></span>
Note, please don't tell me to just add the highlight instead of should-highlight, as i said this is a dumb reduced example. Thanks.
Instead of optionally applying the highlight directive, always apply it and do the optional wrapping inside that directive. The optional wrapping is achieved with an ng-if and a boolean passed from myDirective to myHighlight via markup:
<div my-highlight="someBooleanValue">some text</div>
The myHighlight template:
<div ng-if="actuallyTransclude" style="border:1px solid red">
<span ng-transclude></span>
</div>
<div ng-if="!actuallyTransclude" ng-transclude></div>
Working jsfiddle: http://jsfiddle.net/wilsonjonash/X6eB5/
Sure. When you specify the transclude option, you know that you can declaratively indicate where the content should go using ng-transclude.
In the linking function of the directive, you will also get a reference to a transclude function (https://docs.angularjs.org/api/ng/service/$compile, see link section):
function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
The transcludeFn will return the transcluded content, so you can conditionally insert that were and when you want to in the link function of your directive.
Example (http://jsfiddle.net/DKLY9/22/)
HTML
<parentdir flg="1">
Child Content
</parentdir>
JS
app.directive('parentdir', function(){
return {
restrict : 'AE',
scope: {
flg : "="
},
transclude : true,
template : "<div>Parent {{childContent}} Content</div>",
link : function(scope, elem, attr, ctrl, transcludeFn){
if (scope.flg==1){
scope.childContent="Include Me instead";
}
else {
scope.childContent = transcludeFn()[0].textContent;
}
}
}
});
This is a simplified example. To get a better idea of how to use the transclude function, refer to the following : http://blog.omkarpatil.com/2012/11/transclude-in-angularjs.html
When I approach these kind of problems I just look at what angular did. Usually their source code is very readable and easy to re-use. ngTransclude is no different:
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngTransclude.js
I leave the rest to you. You can either create your own transclusion directive that receives also a condition, or just duplicate the code into your specific directive when the if condition is true.
If you still have trouble, please let me know and we'll set up a plunker.

Resources