I have a directive defined that contains a function binding:
angular
.module('my.module')
.directive("myDirective", [
function () {
return {
controller: 'MyDirectiveController',
templateUrl: 'controls/myDirective/myDirective.html',
replace: true,
restrict: 'E',
scope: {
openFunction: '&'
}
};
}]);
in my source html, I'm defining the directive like so:
<my-directive
open-function="openDrawer(open)"
</my-directive>
Then, in my directive controller, I'm calling it like this:
$scope.openFunction({
open: function() {
doSomething()
.then(function () {...})
.finally(function () {...});
}
});
And here's the parent controller openDrawer function:
$scope.openDrawer = function (open) {
$scope.alerts = null;
$scope.showActions = false;
if (service.editing) {
service.closeAndSave();
openDrawerAfterDelay(open);
} else if (otherService.editing) {
otherService.commit();
openDrawerAfterDelay(open);
} else {
open();
}
};
The problem is, when my directive controller calls the $scope.openFunction() function, nothing happens. Am I able to pass a function, to the bound function like this?
This appears to be something unrelated. I've reproduced the code in plunker here: http://plnkr.co/edit/SuzCGw5WVRClEwEcA17l?p=preview
Possible leads that I can see without seeing the entire code are:
I'm mocking the service and otherService, so the if conditions are always false - the problem may lie in the calls within the if blocks.
I'm unsure of what the doSomething promise is doing, this may be a cause.
Related
HTML :
<div id="idOfDiv" ng-show="ngShowName">
Hello
</div>
I would like to call the function which is declared in my controller from my directive.
How can I do this? I don't receive an error when I call the function but nothing appears.
This is my directive and controller :
var d3DemoApp = angular.module('d3DemoApp', []);
d3DemoApp.controller('mainController', function AppCtrl ($scope,$http, dataService,userService,meanService,multipartForm) {
$scope.testFunc = function(){
$scope.ngShowName = true;
}
});
d3DemoApp.directive('directiveName', [function($scope) {
return {
restrict: 'EA',
transclude: true,
scope: {
testFunc : '&'
},
link: function(scope) {
node.on("click", click);
function click(d) {
scope.$apply(function () {
scope.testFunc();
});
}
};
}]);
You shouldn't really be using controllers and directives. Angularjs is meant to be used as more of a component(directive) based structure and controllers are more page centric. However if you are going to be doing it this way, there are two ways you can go about it.
First Accessing $parent:
If your directive is inside the controllers scope you can access it using scope.$parent.mainController.testFunc();
Second (Preferred Way):
Create a service factory and store your function in there.
d3DemoApp.factory('clickFactory', [..., function(...) {
var service = {}
service.testFunc = function(...) {
//do something
}
return service;
}]);
d3DemoApp.directive('directiveName', ['clickFactory', function(clickFactory) {
return {
restrict: 'EA',
transclude: true,
link: function(scope, elem) {
elem.on("click", click);
function click(d) {
scope.$apply(function () {
clickFactory.testFunc();
});
}
};
}]);
Just a tip, any time you are using a directive you don't need to add $scope to the top of it. scope and scope.$parent is all you really need, you will always have the scope context. Also if you declare scope :{} in your directive you isolate the scope from the rest of the scope, which is fine but if your just starting out could make things quite a bit more difficult for you.
In your link function you are using node, which doesn't exist. Instead you must use element which is the second parameter to link.
link: function(scope, element) {
element.on("click", click);
function click(d) {
scope.$apply(function() {
scope.testFunc();
});
}
In my html page I have a button and a directive snippet like so:
<button ng-click="showProfile();"></button>
<profile ng-if="isProfile==true"></profile>
In my controller I have initialized the $scope.isProfile variable = false and have the function called by the button:
$scope.showProfile = function(contact) {
$scope.contact = contact; // this object needs to get passed to the controller that the directive initiates, but how??
$scope.isProfile = true;
};
In my app I have a directive defined as such...
app.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
controller: contactsProfileController,
link:function(scope, element, attrs) {
console.log('k');
}
};
});
Everything is working but I can't figure out how to pass the $scope.contact object to the controller that the directive references.
I've tried adding scope:scope to the return {} of the directive but with no luck. Do I need to do something in the link function? I've spent the entire day reading about directives and am exhausted so any tips would be greatly appreciated!!!
Thanks in advance for any help!
Here's what the controller that's being called from the directive looks like as well:
var contactsProfileController = function($scope,contact) {
$scope.init = function() {
console.log($scope.contact); //this should output the contact value from the showProfile function.
};
....
}
try this on your directive.
<profile ng-if="isProfile==true" contact="contact"></profile>
and add this to the scope
app.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
scope: {
contact: '=contact'
}
controller: contactsProfileController,
link:function(scope, element, attrs) {
console.log('k');
}
};
});
But I see a couple of issues from your code:
- your showProfile function is expecting a "contact" argument that is not being passed from the button directive, so it will be undefined.
- you are injecting a "contact" dependency on your contactsProfileController controller. Do you have a service / factory declared with that name?
Instead of contact: '#contact', do contact: '=contact'
Since your custom directive is a "component" of sorts, it is a good idea to use an isolate scope and pass the necessary data (i.e. contact) via attributes.
E.g.:
<button ng-click="showProfile(...)"></button>
<profile contact="contact" ng-if="isProfile"></profile>
$scope.showProfile = function (contact) {
$scope.contact = contact;
$scope.isProfile = true;
};
.directive('profile', function () {
return {
restrict: 'ECMA',
scope: {contact: '='}
templateUrl: '/contacts/profile',
controller: contactsProfileController
};
});
Then, the property will be available on the scope (e.g. contactsProfileController's $scope):
var contactsProfileController = function ($scope) {
$scope.$watch('contact', function (newValue) {
// The `contact` has changed, do something...
console.log($scope.contact);
});
...
};
Both of your responses were incredibly helpful and I was able to get things working in a matter of minutes after reading your posts. Thank you so much!!!
Adding contact="contact" into the directive placeholder was key as was adding the scope object to the actual directive code.
So I ended up with:
<profile ng-if="isProfile===true" contact="contact"></profile>
and
.directive('profile', function () {
return {
templateUrl: '/contacts/profile',
restrict: 'ECMA',
controller: contactsProfileController,
scope: {contact: '='},
link:function(scope, element, attrs) {
}
};
});
How does one abstract a directive properly?
As a really basic example, let's say I have this:
http://plnkr.co/edit/h5HXEe?p=info
var app = angular.module('TestApp', []);
app.controller('testCtrl', function($scope) {
this.save = function() {
console.log("hi");
}
this.registerListeners = function() {
console.log('do stuff to register listeners');
}
this.otherFunctionsNotToBeChangedWithDifferentInstances() {
console.log('these should not change between different directives')
}
return $scope.testCtrl = this;
});
app.directive("tester", function() {
return {
restrict: 'A',
controller: 'testCtrl',
template: '<button ng-click="testCtrl.save()">save</button>'
};
});
The tester directive has some methods on it, but only two will be changed or used depending on where the directive is placed. I could pass in the function as a directive attribute, but I am wondering if there is a better way to do this. I have been looking at providers, but I am unsure how or if those would even fit into this.
Instead of letting your directive assume that testCtrl.save() exist on the scope, you would pass in that function as an attribute. Something like this: http://jsbin.com/jidizoxi/1/edit
Your directive binds the value of the my-on-click attribute as a callable function. Your template passes in the controllers ctrlOnClick() function, and when the buttons ng-click calls myOnClick() Angular will call ctrlOnClick() since they are bound to each other.
EDIT:
Another common approach is to pass in a config object to the directive. So your controller would look something like:
$scope.directiveConfig = {
method1: function() { ... },
method2: function() { ... },
method3: function() { ... },
...
}
And your template:
<my-directive config="directiveConfig"></my-directive>
The directive then gets a reference to that object by:
scope: {
config: '='
}
The directive can then call methods on the object like this: $scope.config.method1().
I am trying to load language constants from service to a directive and show them to user.
I have discovered that if I just use {{}} in div, then the text is not rendered.
However, by adding any character, i.e. '.' will make it load.
I would be grateful, if someone can explain, what is going on behind scenes and why I need those extra characters.
Directive code
directive('projectHeader', ['LangService', function(langService) {
return {
restrict: 'E',
replace: true,
scope: true,
link: function postLink($scope, tElement, tAttrs, controller) {
$scope.lang = langService.getLocalisedStrings();
},
templateUrl: "app/header.html"
};
}])
header.html
<div class="header">{{lang.header}}.</div>
LangService definition
angular.module('project.services').factory('LangService', ['$http', function ($http) {
var langConstants;
return {
init: function(lang) {
$http.get("app/lang/"+ lang + ".properties").then(function(response){
langConstants = response.data;
});
},
getLocalisedStrings: function () {
return langConstants;
}
};
}]);
You might have a race between the postLink and the $http in the init method. Try adding a watch on getLocalisedStrings() in the directive so that $scope.lang gets updated as soon as getLocalisedStrings() returns some data.
I have a directive and a controller. The directive defines a function in its isolate scope. It also references a function in the controller. That function takes a callback. However, when I call it from the directive and pass in a callback, the callback is passed through as undefined. The code below will make this more clear:
Directive
directive('unflagBtn', ["$window", "api",
function($window, api) {
return {
restrict: "E",
template: "<a ng-click='unflag(config.currentItemId)' class='btn btn-default'>Unflag</a>",
require: "^DataCtrl",
scope: {
config: "=",
next: "&"
},
controller: ["$scope",
function($scope) {
$scope.unflag = function(id) {
$scope.next(function() { //this callback does not get passed
api.unflag(id, function(result) {
//do something
return
});
});
};
}
]
};
}
]);
Controller
controller('DataCtrl', ['$rootScope', '$scope', 'api', 'dataManager', 'globals',
function($rootScope, $scope, api, dataManager, globals) {
...
$scope.next = function(cb) { //This function gets called, but the callback is undefined.
// do something here
return cb ? cb() : null;
};
}
]);
HTML
<unflag-btn config="config" next="next(cb)"></unflag-btn>
I've read here How to pass argument to method defined in controller but called from directive in Angularjs? that when passing parameters from directives to controller functions, the parameters need to be passed in as objects. So I tried something like this:
$scope.next({cb: function() { //this callback does not get passed still
api.unflag(id, function(result) {
//do something
return
});
}});
But that did not work. I am not sure if this matters, but I should note that the directive is placed inside a form, which in its place is inside a controller. Just to illustrate the structure:
<controller>
<form>
<directive>
<form>
<controller>
Hope this is clear and thanks in advance!
Try this
controller: ["$scope",
function($scope) {
$scope.unflag = function(id) {
$scope.next({
cb: function() { //this callback does not get passed
api.unflag(id, function(result) {
//do something
return;
});
}
});
};
}
]
So I unintentionally figured out whats wrong after not being able to pass an object back to the controller as well. What happened, (and what I probably should have mentioned in the question had I known that its relevant) is that the parent scope of this directive unflagbtn is actually the scope of another directive that I have, call it secondDirective. In its turn the secondDirective is getting its scope from "DataCtrl". Simplified code looks like this:
directive("secondDirective", [function(){
require: "^DataCtrl" // controller
scope: {
next: "&" // function that I was trying to call
}
...
// other code
...
}]);
directive("unflagbtn", [function(){
require: "^DataCtrl" // controller
scope: {
next: "&"
},
controller: ["$scope", function($scope){
$scope.unflag = function(){
$scope.next({cb: {cb: callBackFunctionIWantedToPass}); // this is what worked
}
}
}]);
So passing a callback in that manner solved my problem as it made its way back to the controller. This is ugly most likely due to my poor understanding of angular, so I apologize as this is most likely not the correct way to do this, but it solved my problem so I though I'd share.
Cheers,