Angular JS passing parameter to scope function from directive - angularjs

Assume that I have a directive like this
<div my-directive callback='doSomething(myArg)'></div>
angular.module('directives').directive('myDirective', function() {
return {
restrict: 'A',
scope: {
callback: '&'
},
link: function(scope, element, attrs) {
element.bind('someEvent', function() {
scope.callback({myArg: 'bla'});
});
}
}
});
If I want to pass a parameter to my scope's function, I have to do scope.callback({myArg: 'bla'}). I wonder if there's a way pass the argument without having to specify its name?

Use can use shared service in this case and inject it to directive:
angular.module("yourAppName", []).factory("mySharedService", function($rootScope){
var mySharedService = {};
mySharedService.values = {};
mySharedService.setValues = function(params){
mySharedService.values = params;
$rootScope.$broadcast('dataPassed');
}
return mySharedService;
});
And after inject it to directive. For example:
app.directive('myDirective', ['mySharedService', function(mySharedService){
return {
restrict: 'C',
link: function (scope, element, attrs) {
mySharedService.setValues(//some value//);
}
}
}]);
Then, you can get necessary value in controller.
function MyCtrl($scope, mySharedService) {
$scope.$on('dataPassed', function () {
$scope.newItems = mySharedService.values;
});
}

Related

Angularjs controller required by directive can not be found in transclude content

Recently I use angular to develop a directive, there is an directive which like ng-repeat to generate some records, I used transclude to implement it. but it raise an error that "Controller 'aArea', required by directive 'bSpan', can't be found!".
1. ModuleA code
var moduleA = angular.module("moduleA", []);
moduleA.directive("aArea", function () {
return {
restrict: 'E',
transclude:'element',
scope: {
amount:"="
},
template: '<div id=\"cc\" ng-transclude></div>',
controller: function ($scope,$element,$attrs) {
this.getData = function (data) {
return data + " is ok";
}
},
compile: function (tElement, attrs, linker) {
var parentElement = tElement.parent();
return {
pre: function () {
},
post: function (scope) {
linker(scope.$parent,function (clone,scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
}
}
}
}
});
moduleA.directive("bSpan", function () {
return {
restrict: 'E',
scope: {
data: "=",
},
template: '<span style=\"background-color:gray;color:orange\">{{data}}</span>',
require: "^aArea",
link: function ($scope, $element, $attrs, controller) {
var data = "abc";
}
}
});
2. ModuleB COde
var moduleB = angular.module("moduleB", []);
moduleB.directive("myItem", function () {
return {
restrict: 'E',
scope: {
item: "=",
itemTemplate: '='
},
priority: 1000,
terminal:false,
template: '<ng-include src=\"itemTemplate\"/>',
controller: function ($scope, $element, $attrs) {
var data = "";
}
}
})
3. ModuleC Code
var moduleC = angular.module("moduleC", ["moduleA", "moduleB"]);
moduleC.controller("Ctr", function ($scope) {
$scope.item = {};
$scope.item.dataAmount = 1000;
$scope.item.templateUrl = "item-template.html";
})
4. Html Code
<body>
<div ng-app="moduleC">
<div ng-controller="Ctr">
<a-area>
<my-item item="item" item-template="item.templateUrl"></my-item>
</a-area>
</div>
</div>
</body>
5. template code
<div>
<span style="display:block">hello every one</span>
<b-span data="item.dataAmount"></b-span>
</div>
You should not use the transclude function (that you called linker) of the compile function - it is deprecated.
From $compile documentation:
Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.
Following this guidance (and a few other minor changes for the better), change the aArea directive as follows:
compile: function(tElement, tAttrs) {
// don't use the template element
//var parentElement = tElement.parent();
return function(scope, element, attrs, ctrls, transclude) {
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
};
}
In fact, you don't even need the transclude function at all and you don't need to do transclude: "element". You could just change to transclude: true and use <div ng-transclude> 3 times in the template.

How can I pass variables from an isolated scope directive to a child?

I have two isolated scope directives. Ideally I like both to work independently and not require any custom templates. The first directive is going to be page scroll watcher, when it hits a certain point I want it to trigger an update in the other directive. Is it possible for a child directive to watch a variable in the parent directive?
I've created a simple plunkr to illustrate the issue, http://plnkr.co/edit/wwfBzmemyrj1r1R54riM?p=preview
/*
<div ng-outer>Outer directive {{myvar}}
<div ng-inner="myvar">Inner directive</div>
</div>
*/
app.directive('ngOuter', [ '$timeout', function ($timeout) {
var directive = {
restrict: 'A'
,scope:{}
}
directive.link = function (scope, element, attrs) {
$timeout(function(){
scope.myvar = "test 001"
},1000)
}
return directive;
}]);
app.directive('ngInner', [ function () {
var directive = {
restrict: 'A'
,scope:{ data: '=ngInner', myvar: '=myvar' }
}
directive.link = function (scope, element, attrs) {
scope.$watch('data', function(newVal, oldVal){
if(newVal)
element.text("new inner val", newVal);
});
scope.$watch('myvar', function(newVal, oldVal){
if(newVal)
element.text("new myvar", newVal);
});
}
return directive;
}]);
Solved this issue by using
angular.element(element.parent()).isolateScope();
The child directive can access the scope of the parent directive and watch variables etc.
http://plnkr.co/edit/RAO6q81ZE4tClMDMiLFb?p=preview
First approach of passing variable from parent directive to the child is by using an 'attr' in child scope with '#' and initialize it in parent controller as following:
var app = angular.module('app', []);
app.directive('ngOuter', [ '$timeout', function ($timeout) {
return{
restrict: 'E',
scope:{},
template: '<ng-inner attr="{{myvar}}">Inner directive {{myvar}}</ng-inner>',
controller: ['$scope', function($scope) {
$scope.myvar = "test 001";
}],
link: function (scope, elem, attrs) {
}
}
}]);
app.directive('ngInner', [ function () {
return{
restrict: 'E',
scope:{'attr' : "#"},
link: function (scope, element, attrs) {
console.log(scope.attr);
}
}
}]);
html:
<ng-outer></ng-outer>
2nd approach is utilizing a function in a parent controller which returns the "myvar" value and call that function in child directive:
app.directive('ngOuter', [ '$timeout', function ($timeout) {
return{
restrict: 'E',
scope:{},
template: '<ng-inner attr="{{myvar}}">Inner directive {{myvar}}</ng-inner>',
controller: ['$scope', function($scope) {
$scope.myvar = "test 001";
this.getMyvar = function() {
return $scope.myvar;
};
}],
link: function (scope, elem, attrs) {
}
}
}]);
app.directive('ngInner', [ function () {
return{
restrict: 'E',
require: '^ngOuter',
scope:{'attr' : "#"},
link: function (scope, element, attrs, parentDirCtrl) {
console.log(parentDirCtrl.getMyvar());
}
}
}]);
3rd Approach: you can inject a service to both inner and outer directives.Then use the service.
var app = angular.module('app', []);
app.service('myService', [
function() {
var service = {
myvar: 'test001',
setMyVar: function(value) {
this.myvar = value;
},
getMyVar: function() {
return this.myvar;
}
}
return service;
}
]);
app.directive('ngOuter', ['$timeout', 'myService',
function($timeout, myService) {
var directive = {
restrict: 'A',
scope: {}
}
directive.link = function(scope, element, attrs) {
$timeout(function() {
scope.myvar = myService.getMyVar();
}, 1000)
}
return directive;
}
]);
app.directive('ngInner', ['myService',
function(myService) {
var directive = {
restrict: 'A',
scope: {}
}
directive.link = function(scope, element, attrs) {
var variable = myService.getMyVar();
console.log("myvar", variable);
}
return directive;
}
]);

Access require controller in a directive controller

app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
mainCtrl.funcA()
}
};
});
I don't want to use the link method but the controller method.
Is there a way to get the mainCtrl in the controller method of the directive addProduct.
something like:
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
controller: function (scope, mainCtrl) {
mainCtrl.funcA()
}
};
});
You'd still need to use the link function because the controllers are injected there. What you could, however, is request your directive's own controller and then set the other required controller as its property:
app.directive('addProduct', function () {
return {
restrict: 'E',
require: ['addProduct','^mainCtrl'],
controller: function ($scope) {
// this.mainCtrl is still not set here
// this.mainCtrl.funcA(); // this will cause an error
// but typically it is invoked in response to some event or function call
$scope.doFuncA = function(){
this.mainCtrl.funcA();
}
},
link: function(scope, element, attrs, ctrls){
var me = ctrls[0], mainCtrl = ctrls[1];
me.mainCtrl = mainCtrl;
}
};
});
Since AngularJS 1.5, you can use the $onInit lifecycle hook of the controller. As written in the documentation of require, when defining require as an object and setting bindToController to true, the required controllers are added to the controller as properties after the controller has been constructed, but before the $onInit method is run. So the code would look like this:
app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: {
myParentController: '^mainCtrl'
},
bindToController: true,
controller: function ($scope) {
this.$onInit = function() {
this.myParentController.funcA();
};
}
};
});
Here is my solution:
app.directive('mainCtrl', function () {
return {
controllerAs: 'main',
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
controller: function ($scope) {
$scope.main.funcA();
}
};
});
Pass the controller to the scope on the link function then accessing the scope on controller. Like this:
app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
scope.ctrl=mainCtrl;
},controller:function($scope){
$scope.ctrl.funcA();
}
};
});

AngularJS: How to get directive's name in the linking function?

I would like to use directive's name inside the linking function. How could I get it?
app.directive('myDirective', function() {
return {
link: function(scope, element, attrs) {
// How could I get directive's name here (i.e. 'myDirective')?
}
};
});
It is possible inside compile function of directive.
directives.directive('myNamedDir', ['$compile', function ($compile) {
return {
compile: function(cElem, cAttrs, transclude) {
var name = this.name;
return function linkFunction(){
//use name
}
}
}]);
Simply define it outside the injection:
var name = 'myDirective';
app.directive(name, function() {
return {
link: function(scope, element, attrs) {
console.log(name); // --> myDirective
}
};
});
app.directive('myDirective', function() {
return {
link: function(scope, element, attrs) {
console.log(arguments.callee.directiveName); // --> myDirective
}
};
});
the arguments.callee.caller property deprecated in JavaScript

How to use require option in directives

In documentation I can read next for the require option:
When a directive uses this option, $compile will throw an error
unless the specified controller is found. The ^ prefix means that this
directive searches for the controller on its parents (without the ^
prefix, the directive would look for the controller on just its own
element).
So I try to use it:
<div ng-sparkline></div>
app.directive('ngCity', function() {
return {
controller: function($scope) {}
}
});
app.directive('ngSparkline', function() {
return {
restrict: 'A',
require: '^ngCity',
scope: {},
template: '<div class="sparkline"><h4>Weather </h4></div>',
link: function(scope, iElement, iAttrs) {
// get weather details
}
}
});
But I have an error if my html have not ng-city attribute, so if I need controller of another directive - need to add exactly same attribute in html, but why (<div ng-sparkline ng-city="San Francisco"></div>)? And it looks on another directive's controller with this name (directive!!!) but not at controller with this name, is that true? Thanks. Just want to make it clear
With require you can get the controller of another (cooperating) directive. The controller in Angular is not semantically a function, but an object constructor, i.e. called essentially as var c = new Controller() (this is a simplification for the sake of clarity). Since the controller is an object, it can have properties and methods. By requiring the controller of another directive, you gain access to those properties/methods. Modifying your example to demonstrate:
app.directive('ngCity', function() {
return {
controller: function($scope) {
this.doSomething = function() {
...
};
}
}
});
app.directive('ngSparkline', function() {
return {
restrict: 'A',
require: '^ngCity',
scope: {},
template: '<div class="sparkline"><h4>Weather </h4></div>',
link: function(scope, iElement, iAttrs, ngCityController) {
// use the controller, e.g.
ngCityController.doSomething();
}
}
});
In your case, the city would be a property of the controller of the ngCity directive, exposed as a property. It will be read by the ngSparkline to know for which city the graph is about.
<b> added directives.js</b>
<code>
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngCity',
scope: {
ngCity: '#'
},
templateUrl: '/scripts/templates/tpl.html',
controller: ['$scope', '$http', function ($scope, $http) {
var url = "https://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=imperial&cnt=7&callback=JSON_CALLBACK&q=";
console.log(url + $scope.ngCity);
$scope.showTemp = function () {
$scope.getTemp($scope.ngCity);
};
$scope.getTemp = function (city) {
$http({
method: 'JSONP',
url: url + city
}).success(function(data) {
var weather = [];
angular.forEach(data.list, function(value){
weather.push(value);
});
$scope.weather = weather;
});
}
}],
link: function (scope, iElement, iAttrs, ctrl) {
scope.getTemp(iAttrs.ngCity);
scope.$watch('weather', function (newVal) {
if (newVal) {
var highs = [];
angular.forEach(scope.weather, function (value) {
highs.push(value.temp.max);
});
//chartGraph(iElement, highs, iAttrs);
}
});
}
}
}).directive('ngCity', function () {
return {
controller: function ($scope) {
//console.log("hello");
}
}
});
</code>
<b> and added tpl.htm</b>
<code>
<div class="sparkline">
<input type="text" data-ng-model="ngCity">
<button ng-click="showTemp()" class="btn1">Check {{ngCity}}</button>
<div style="color:#2743EF">{{weather}} ÂșC</div>
<div class="graph"></div>
</div>
</code>

Resources