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

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

Related

Angular: add ng-click inside directive

In my directive i using appendTo and ng-click inside it:
$("<div>" + "<a href='' ng-click='addIt()' + user + "</a></div>").appendTo '.user-class'
And ng-click doesn't work. Should i use $compile? How to use it in that case?
this code is a part of directive that make dropdown menu from json. There is no option to move this to directive template, so need to find solution how to make 'ng-click' works here.
Like Matthew, I am most curious as to which part(s) cannot be moved out into an external template / internal template function. It doesn't make much sense to me to have to re $compile a directive only to add a ng-click handler.
The senseful way would be to add the ng-click directive to a template. Either an external one, or a function returning a string value in the directive definition object.
Running $compile after the link step has finished is not a good idea performance wise, seeing as how it recompiles the entire DOM element your directive is attached to. Avoid doing so for as long as you possibly can.
.directive('myDir', function () {
return {
template: function (/*element, attrs*/) {
return '<div>{{::user}}</div>';
},
link: function (scope, el, attrs) {
scope.user = 'John';
}
};
});
This would result in the following DOM structure:
<div>
John
</div>
jsbin
Yes you will need $compile , inject $compile in directive and you can do it like this :
var link = $compile("<div>" + "<a href='' ng-click='addIt()'" + user + "</a></div>")(scope);
link.appendto('.user-class');

Common directive ng-click guidance needed

I have a directive which consists of a form text element and a continue button along with the associated controller etc. This directive is going to be used in about 5 different pages, but on each page it is used the continue button will do something different.
My question is where can/should I put the code for the continue button if it does different things for each page?
Since its a directive I cant simply pass a different function into ng-click depending on what page im on (ie, if i simply replicated the code on each page it is used I could simply change the function called on ng-click and have that function in each of the page controllers.
Hopefully Im not being too vague with my question and you can make sense of what im asking. If not just say so and ill try to explain in more detail.
I would really appreciate some guidance on this matter.
Thanks.
There are two ways that you can do it. If you are creating your directive as a true component you can use isolated scope with & binding that binds to an expression.
Assume your directive looks like
<div do-work on-click="save()"></div>
and the generated html
<div>
<input ...>
<button ng-click="doAction()"><button>
</div>
The directive scope will be defined
scope:{
onClick:'&'
}
In your directive controller or link function you need to implement the button doAction, which in turns evaluates the onClick action
scope.doAction=function() {
scope.onClick({//if params are required});
}
Now you have linked the parent through the direct onClick reference. One thing to remember here is that this creates a directive with isolated scope.
In case you do not want isolated scope created you need to use
scope.$eval(attr.onClick); // this evaluates the expression on the current scope.
Hope this helps.
Ideally you should not create directives which are not re-usable.
In your case, you may do it like following -
create an isolated scope in the directive
add a function to be called and pass the page/ page id as parameter
call functions in controller based on parameter
Directive
myApp.directive('someDirecive', function () {
return {
// restrict options are EACM. we want to use it like an attribute
restrict: 'A',
// template : <inline template string>
// templateUrl = path to directive template.
// templateUrl: '',
scope: {
onButtonClick : '&'
},
controller: function ($scope, $element, $attrs, $transclude) {
$scope.onButtonClick = function(pageId) {
if (pageId == 1) {
// do something
}
else if (pageId == 2) {
// do something
}
}
},
//link: function (scope, iElement, iAttrs) {
//}
};
});
HTML
<div some-directive on-button-click="DoSomething(1)" />

angularjs custom directive isolated scope one way data binding doesn't work

i am a new to angularjs, I read some literature and followed a lot of tutorials, but i am still have the feeling that i completely confused.
My current issue is with custom directive and isolated scopes. All i trying to do is pass "strings" with # binding to my directives that use isolated scopes and I can't understand what am i doing wrong. Specifically WHY when i use template everything just works fine and when the template already in the DOM the one way data binding doesn't work.
JSBin fiddle link
major parts from my code:
HTML
<div my-directive my-title="TITLE ONE WAY Data Binding">
<div>
<div>This directive is <span style="color:red;">NOT using template</span></div>
<div>
$scope.title = <small><pre>{{title}}</pre></small>
</div>
</div>
</div>
<div my-directive-with-template my-title="TITLE ONE WAY Data Binding"
>
<!-- this directive use a template -->
</div>
JS
var app = angular.module('app', []);
app.directive('myDirective', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('non template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('non template directive controller:',$scope.title);
}]
};
});
app.directive('myDirectiveWithTemplate', function() {
return {
restrict: 'AE',
scope:{
title: "#myTitle"
},
link: function(scope, ele, attrs, c) {
console.log('using template directive link:',scope.title,attrs.myTitle);
},
controller:['$scope', function($scope){
console.log('using template directive link:',$scope.title);
}],
template:'<div><div>This directive is using template</div><div>$scope.title = <small><pre>"{{title}}"</pre></small></div></div>',
replace:true
};
});
JSBin fiddle link
In your non-template scenario the title is not being bound to any scope and therefore not showing anything.
What you call the DOM template is really HTML outside the directive that has no access to it's isolated scope. You could embed this div inside a controller and then title could be bound to the controller's $scope.title
For what I understand it only makes sense to create an isolated scope to make it available to the directive's template.
Clarification
Isolated scopes allow the directive to have state independent of the parent scope (avoiding it's pollution) and also avoiding sharing this state with sibling directives.
Supposing you're creating this directive to reuse that piece of UI somewhere else in your code, you start by creating its template with the shared HTML.
Ok, but you need to go a bit further and parameterize it passing some data to it.
You can then use attributes on the directive to communicate with the outside (parent scope, or just to pass static data).
The directive's template can now bind to this data without needing to have any knowledge of it's "outside world", and it's done through it's isolated scope.
Conclusion, why create an isolated scope, if not to provide the template with this data?
Hope I've made this a bit clear :)
Now after thinking a bit about my affirmation... well you could also create a directive without any template, by using the compile or link function and do it manually through DOM manipulation. And in this case it might make sense to have an isolated scope for the reasons presented above :)

angularJS directive with isolated scope, attribute binding doesn't work

Please see this jsfiddle: http://jsfiddle.net/viro/DK5pC/3/
What I did looks right compared to the tutorials and replies I've found, so I'm sure I'm overlooking something trivial.
I'm trying to do a directive on a html element, that will create a sibling div to display a message associated with the original element.
for example, for this html :
<input ng-model="inp" tst-msg="message" />
I would create as a sibling element:
<div class="msg">Msg:<span ng-bind="tstMsg"></span></div>
I was hoping that tstMsg in the div's scope would be bound to message in the input's scope.
Here's what the directive looks like :
angular.module('tst', [])
.directive('tstMsg', function(){
var template = "<div class='msg' >Msg:<span ng-bind='tstMsg'></span></div>";
var link = function(scope,element,attrs) {
element.parent().append(template);
console.log("link called");
};
return {
restrict: 'A',
scope: {
tstMsg: '='
},
link: link
};
});
Well that doesn't work and I can't figure out why.
You need to $compile the template you're adding to the DOM. Angular hasn't had a chance to add it's handlers, for instance the ng-bind directive to that part of the dom.
So instead of just adding the element like this:
element.parent().append(template);
These steps will let Angular process your template and then add it.
newe = angular.element(template);
$compile(newe)(scope);
element.parent().append(newe);
Updated fiddle

Custom directive interfering with ngClick

In this fiddle, why does the ngClick in the top link work, but the ngClick in the link to which I have added a custom directive completely fail to function?
<a class="regular" ng-click="clickTheLink()">A regular ng-click link</a>
<a class="disableable" disable="disableTheLink" ng-click="clickTheLink()">A disableable link!</a>
As far as I can tell, nothing I'm doing in the directive should be interfering at all with ngClick behavior, as all it does is manipulate CSS classes:
app.directive('disableable', function(){
return {
restrict: 'C',
scope: { disable: '&' },
link: function (scope, elem, attrs) {
scope.$watch(scope.disable, function (val) {
if (val){
elem.addClass('disabled');
elem.removeClass('enabled');
}
else {
elem.addClass('enabled');
elem.removeClass('disabled');
}
});
}
};
});
The thing is, each DOM element only has one scope. So if any directive uses isolate scope like you're using here, that becomes the one and only scope on the element. That scope is completely disconnected from any parent scopes, and in your example, clickTheLink isn't in there.
The simple answer is to not use isolate scope. It's a real nice syntax but you can do everything it does manually. For the '&' params, you can just use the parse service to parse the attribute expressions.
See updated working fiddle:
http://jsfiddle.net/SNQQV/3/
It's because you're creating an isolate scope on line 14 of the fiddle, the clickTheLink function only exists in the controller and not in the directive. While I highly suggest against doing it this way, you can quickly access the parent scope via $parent
<a class="disableable" target="_blank" disable="disableTheLink" ng-click="$parent.clickTheLink()">A disableable link!</a>
Putting this code in allows the fiddle to work correctly. Here's the fiddle of it: http://jsfiddle.net/bpN9b/10/
My suggestion would be to look into how ngClass work as well as ngDisabled. I think both of those will allow you to not use this directive at all.

Resources