I know you can have optional property using a question mark like x:"=?" and then you can check if it is specified by checking if x is undefined or null.
How can I do similar thing for a function? Oftentimes, I want to hide a control if the function is not specified. I have to define another property for this purpose to workaround this problem. I wonder if there is a way to save this extra property.
I found an answer to this question at here. Here is a recap in case somebody is interested:
In directive:
scope: {
callback: '&'
},
link: function(scope, elem, attrs) {
scope.hasCallback = function() {
return angular.isDefined(attrs.callback);
}
}
In html:
Call me back
I like it very much because it saves me an extra parameter.
Related
I was wondering if it was possible to pass a function foo() as an attribute func="foo()" and have it called this.func() inside of the polymer element?
<foo-bar func="foo()"></foo-bar>
Polymer({
is: 'foo-bar',
properties: {
func: Object,
},
ready: function() {
this.func();
}
});
I've been trying to get this working for ages with no luck.
Thanks in advance.
<foo-bar func="foo()"></foo-bar>
Polymer({
is: 'foo-bar',
properties: {
func: {
type: String, // the function call is passed in as a string
notify: true
},
attached: function() {
if (this.func) {
this.callFunc = new Function('return '+ this.func);
this.callFunc(); // can now be called here or elsewhere in the Polymer object
}
});
So the trick is that "foo( )" is a string when you first pass it to the Polymer element. I fought with this for a while as well and this is the only way I could find to get it done. This solution creates a function that returns your function call, which you assign as the value of one of your polymer element properties.
Some people might say you shouldn't use the Function constructor because it is similar to eval( ) and.... well you know, the whole 'eval is evil' thing. But if you're just using it to return a call to another function and you understand the scope implications then I think this could be an appropriate use-case. If I'm wrong I'm sure someone will let us know!
Here's a link to a nice SO answer about the differences between eval( ) and the Function constructor in case it can help: https://stackoverflow.com/a/4599946/2629361
Lastly, I put this in the 'attached' lifecycle event to be on the safe side because it occurs later than 'ready'. I'm not sure if an earlier lifecycle event or 'ready' could be used instead of 'attached'. Perhaps someone can improve this answer and let us know.
I created a directive with a method that should be called from other elements that are not part of the directive. However it looks like this method is not exposed.
Some example jade code to clarify:
//- a controller for the view itself
div(ng-controller="someController")
//- this is part of the view itself, not within the directive
div(ng-repeat="element in elements")
div(ng-click="methodFromDirective(element)") click element {{$index}} to trigger directive
//- this is the directive
div(some-directive)
The someController isn't too important here I think. It has methods but NOT the methodFromDirective(element) one. The methodFromDirective(element) is a method that exists only in the directive.
If I make a directive and put some logging on creation I can clearly see it's created. However the methodFromDirective(element) method isn't exposed so the calls aren't properly triggered.
The methodFromDirective(element) itself will only work on elements from within the directive's template.
some coffeescript to show the definition of the the directive (ignore indentation errors here):
'use strict'
define [], () ->
someDirective = () ->
restrict: 'A'
scope: {
show: '='
}
transclude: false
templateUrl: 'someTemplateHere.html'
controller = ($scope) ->
# exposing the method here
$scope.methodFromDirective(element)->
$scope.theMethod element
link = (scope, element, attr) ->
# this is logged
console.log "init someDirective"
# triggering this method form outside fails
scope.theMethod = (element)->
console.log "method triggered with element", JSON.stringify(element)
I found my issue.
From the angularJS documentation on directives I was looking into the transclude option since that states:
What does this transclude option do, exactly? transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.
I combined transclude=false with the controller function since that exposes the method, again from docs:
Savvy readers may be wondering what the difference is between link and controller. The basic difference is that controller can expose an API, and link functions can interact with controllers using require.
However what I missed completely was that I isolated scope within my directive. From docs:
What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an isolate scope. To do this, we can use a directive's scope option:
So even if you use transclude=false and the controller function you'll still fail to expose methods if you use isolated scope! Lesson learned!
While figuring out what went wrong I also made a fiddle for better understanding: http://jsfiddle.net/qyBEr/1/
html
<div ng-app="directiveScopeExample">
<div ng-controller="Ctrl1">
<p>see if we can trigger a method form the controller that exists in the directive.</p>
<ul>
<li>Method in Controller</li>
<li>Method in Directive</li>
</ul>
<simple-directive/>
</div>
</div>
javascript
angular.module('directiveScopeExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
$scope.methodInController = function(){
alert('Method in controller triggered');
};
})
.directive('simpleDirective', function(){
return {
restrict: 'E',
transclude: false,
controller: function($scope){
$scope.methodInDirective = function(){
// call a method that is defined on scope but only within the directive, this is exposed beause defined within the link function on the $scope
$scope.showMessage('Method in directive triggered');
}
}
// this is the issue, creating a new scope prevents the controller to call the methods from the directive
//, scope: {
// title: '#'
//}
, link: function(scope, element, attrs, tabsCtrl) {
// view related code here
scope.showMessage = function(message){
alert(message);
}
},
//templateUrl: 'some-template-here.html'
};
})
Calling private methods inside directive's link function is very simple
dropOffScope = $('#drop_off_date').scope();
dropOffScope.setMinDate('11/10/2014');
where
$('#drop_off_date') - jQuery function
setMinDate() - private function inside directive
You can call directive function even from outer space.
By default the scope on directive is false meaning directive will use the parent's scope instead of creating a new one. And hence any function or model defined in the directive will be accessible in the parent scope. Check out this.
I think your problem can be solved as follows:
angular.module('directiveScopeExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
$scope.methodInController = function(){
alert('Method in controller triggered');
};
})
.directive('simpleDirective', function(){
return {
restrict: 'E',
scope: false,
link: function(scope, element, attrs, tabsCtrl) {
// view related code here
scope.showMessage = function(message){
alert(message);
}
},
//templateUrl: 'some-template-here.html'
};
This approach might be an issue in case you want to create reusable directives and you are maintaining some state/models in your directive scope. But since you are just creating functions without side-effects, you should be fine.
I've been studying AngularJS and in particular saw the video:
http://www.thinkster.io/pick/IgQdYAAt9V/angularjs-directives-talking-to-controllers
This video presents an example of a directive talking to a controller which I've modified a bit to try and understand if one could also use an isolate scope to get a similar result. Consider an HTML snippet such as:
<div enter="loadMoreTweets()">Roll Over This</div>
and an Angular controller and directive defined as:
app.controller('scopeCtrl', function($scope) {
$scope.loadMoreTweets = function () {
alert("loading more tweets");
}
}).directive('enter', function() {
return {
restrict: "A",
scope: {enter: "#"},
link: function(scope, element, attrs) {
element.bind("mouseenter", function() {
//scope.$apply(attrs.enter);
scope.$apply(scope.enter);
})
}
}
});
Rolling over the DIV causes no errors and has no effect.
If I comment out the isolate scope and use the commented line in the element.bind() rather than the reference to scope.enter then rolling over the DIV causes the alert() to display as expected.
Question: If the "#" isolate scope creates a one-way binding between the attribute's value and the scope's property then I would have expected that scope.enter == attrs.enter. Clearly this isn't true. Why?
The reason for that is that '#' is a one way data binding but it's passed always as a string
scope: { // set up directive's isolated scope
name: "#", // name var passed by value (string, one-way)
age: "=", // age var passed by reference (two-way)
showName: "&" // passed as function
}
The at sign "#" indicates this variable is passed by value. The directive receives a string that contains the value passed in from the parent scope. The directive may use it but it cannot change the value in the parent scope (it is isolated).
I need your help.
I have a directive with a function parameter ( scope: { myParam: '#'}). And I'm passing the parameters in HTML, like my-param="myFunc(param1, param2)"
This works perfectly. But, I need to inject the event object in to the parameters. Does some one know how can I do that?
I tried $provider.annotate and $provider.instantiate, but they did not work because it's taking the reference function in directive. ($a in my case), so it can't get the function arguments.
any idea?
When you're calling a function created by the & isolate scope syntax, you can pass parameters to it as a named map. If your directive is defined like this:
scope: { myParam: '&' },
link: function (scope, el) {
el.on('click', function (e) {
scope.myParam({$event: e});
});
}
and used like this:
<my-directive my-param="console.log($event)"></my-directive>
... you should see the desired behavior.
chrisrhoden's answer is great. I would suggest to expand upon it a bit with the following. Doing so will help prevent ng-click double-firing issues relating to mobile devices and/or AngularJS conflicts with jQuery.
myApp.directive('responsiveClick', function(){
return {
scope: { myCb: '&' },
link: function (scope, el,attr) {
el.bind('touchstart click', function (e) {
e.preventDefault();
e.stopPropagation();
scope.myCb({$event: e});
});
}
}
});
along with the markup as follows:
<a class="mdi-navigation-cancel" my-cb="removeBasketItem($event,item)" responsive-click></a>
have you tried passing it in the original function?
my-param="myFunc($event, param1, param2)"
Background:
I'm trying to run a callback when something inside the code of a directive in AngularJS happen.
Pertinent code:
HTML:
<img-cropper onselect="updateAvatarData" x="100" y="100" src="{{tempAvatar}}" id="avatarCropper"/>
Controller:
$scope.updateAvatarData = function(c){
alert("¡¡¡Funciona!!!!");
};
Directive:
<<more code up here>>
link: function(scope,element, attr) {
scope.wsId = attr.id+'WS';
scope.pvId = attr.id+'preview';
scope.x = attr.x;
scope.y = attr.y;
scope.aspectRatio = scope.x/scope.y;
scope.previewStyle = "width:"+scope.x+"px;height:"+scope.y+"px;overflow:hidden;";
scope.onSelectFn = scope.$eval(attr.onselect);
<<more code down here>>
The problem is in that last line "scope.onSelectFn = scope.$eval(attr.onselect);". That "scope.$eval(attr.onselect);" returns 'undefined'. The attr.onselect works fine, it returns the name of the function typed on the 'onselect' attribute.
I have made others directives with functions passed via attibutes with no problem, but am unable to find what I am doing wrong here.
Thanks in advance
Why you are doing like this when u can easily use '&' feature available with angular
calling method of parent controller from a directive in AngularJS
Still if you want to call parent function like this then you should be using $parse instead of eval see a very below small example when using
link: function (scope,element,attrs) {
var parentGet = $parse(attrs['onselect']);
var fn = parentGet(scope.$parent);
fn();
},
scope.$eval(attr.onselect) should work.
Here is a working fiddle (tested in Chrome) with a minimal link function:
link: function(scope, element, attr) {
scope.onSelectFn = scope.$eval(attr.onselect);
console.log(attr.onselect, ',', scope.onSelectFn);
scope.onSelectFn();
},
The only other thing I can think of is that since onselect is an HTML attribute, maybe it doesn't work on some other browsers. So maybe try using a different attribute name.
By default, $eval only evaluates the given expression against the current scope. You can pass in a different data object to evaluate against, and in your case it is the parent scope. You should call it like this:
scope.onSelectFn = scope.$eval(attr.onselect, scope.$parent);
See the documentation here