angularJS Dynamic scope Definition for an isolated scope custom Directive Definition - angularjs

I have an isolated scope custom directive say
<my_isolate_scope_dir columns="columns" />
And the directive Definition goes something like this.
.directive("myIsolateScopeDir", ["$compile","$templateCache",function($compile,$templateCache){
return {
restrict: 'E',
scope: {
columns: '=',
},
templateUrl: 'customdirective.html',
}
}]);
The question is ,in the directive definition scope:{} can i define one more scope variable dynamically whose value is coming from the parent scope variable columns.
The parent controller can be
$scope.columns=[{"name":"x","isLink":true,"onClickFunction":"clickedx"},{"name":"y","isLink":true,"onClickFunction":"clickedy"}]
$scope.clickedx=fucntion()
{
console.log("x is clicked");
}
i want my custom directive scope definition to be
scope: {
columns: '=',
clickedx: '&', //this is added dynamically to the definition based on the value in columns array
clickedy: '&' //this is added dynamically to the definition based on the value in columns array
},
let me know if this can be achieved in the same way or is there any other simpler way to do this.

You can manually inject the methods into the scope using $parse.
link: function (scope, element, attrs) {
if (scope.columns) {
scope.columns.forEach(function (column) {
if (column.onClickFunction) {
// column.onClickFunction is a string(eg, clickedx), we need to treat this as a function in scope
var fn = column.onClickFunction + "()";
// get a reference to the function defined in the scope
var getParentMethod = $parse(fn);
// Use of Locals: apart from accessing the properties of the scope, you will be able to pass few properties using locals, which will have the precedence over the properties of scope
// For example if the markup is -- <button on-click="test(arg1, arg2)"
// arg1, arg2 needs to be evaluated in the scope
// As isolateScope breaks the inheritance chain, we need to pass element.scope() to the parser function to access the arg1, arg2 properties from controllers scope
// define a method in the scope with the value of column.onClickFunction (eg, clickedx)
scope[column.onClickFunction] = function (locals) {
// when scope.clicedx is executed execute the method defined in the controller.
return getParentMethod(element.scope(), locals);
}
}
})
}
}

Related

Can I have attributes in an Angular directive which uses parent scope?

I have a directive that uses the data from the parent scope. Now, I want to have an attribute in it, but still want to use the parent scope and not transfer all the other data in more attributes and duplicate objects.
var myDirective = function () {
return {
restrict: 'A',
templateUrl: '/Areas/GetAdvice/Templates/Directives/loan-comparison-table-directive.html?1',
//scope: - Don't add anything here to use the scope of the parent
// But how to use an attribute in this way?
controller: [
'$scope', function ($scope) {
// Some Code
}
}
So, how can I add an attribute (independent variable) in the directive, when having the parent scope inherited?
EDIT:
I want just a simple boolean property - something like isDescriptionVisible (true/false). The problem is that I use the same directive twice in my view, so I cannot have a variable in the parent scope, because it will affect both directives and I won't have control on them separately.
You need to create an isolated scope for the directive. This is the exact use case for it. Otherwise you need to create separate variables to track the booleans in the parent scope.
If you don't need to pass boolean value from the parent scope you can do the following:
var myDirective = function () {
return {
restrict: 'A',
templateUrl: '/Areas/GetAdvice/Templates/Directives/loan-comparison-table-directive.html?1',
scope: {}
controller: [
'$scope', function ($scope) {
$scope.isVisible = false;
}
}
If you need to pass it from the parent scope just simply replace
scope: {}
with
scope: {
isVisible: '='
}

AngularJs attributes vs scope in directives

What is the difference while using variables though attributes vs scope while define directives? For example -
angular.module('tModule')
.directive('tModule', function() {
return {
restrict: 'E',
scope: true,
templateUrl: function(element, attributes) {
return attributes.variable1;
}
}
});
versus if I use scope. like below -
angular.module('tModule')
.directive('tModule', function() {
return {
restrict: 'E',
scope: {
variable1: "=variable1",
variable2: "=variable2"
},
templateUrl: function() {
return variable1;
}
}
});
What are the differences and advantages?
There are a few differences based on your example:
In your top example, specifying scope: true means to create a new instance of the parent scope. In the bottom example, using an object syntax means to create an isolated scope. So, when you pass parameters to a directive using the scope syntax like that, you are inherently creating an isolated scope for the directive.
If you want to take advantage of two-way data binding (=) or method invocation (&), then you'd want to use the second method of passing through scope instead of attributes.

angular directive with isolate scope, fields inaccessible

I'm trying to write a directive which takes a scope variable name and assigns to it the result of passing a different named parameter into a function. Below, the files="result" is intended to create a {{result}} variable in the glob isolate scope. The contents of the "matching" variable are to be evaluated in the parent context, and assigned to an isolate 'matching' variable.
the directive then calls a function eventually assigning to the isolate variable pointed to by files (result here) the array returned. expansion of {{result}} could then be used for example in an ng-repeat.
The directive should be reusable without changing the variable names.
This isn't happening. If I assign everything to a parent, I can get it working but need to change the variable names each time.
angular.module('j20-glob', ['api'])
/*
* usage: <glob files="result" matching="/bin/{{prefix}}*">
* {{result}}
* </glob>
* should allow another just after the first without stomping result
* <glob files="result" matching="/something">{{result}}</glob>
*/
.directive('glob', ['$parse', 'api', function($parse, $api) {
return {
priority: 99, // it needs to run after the attributes are interpolated
restrict: 'AE',
scope: {
},
link: function(scope, iElement, iAttributes) {
var indexModel = $parse(iAttributes.files);
iAttributes.$observe('matching', function(value) {
if (!value)
return;
$api.glob(value).then(function(res) {
indexModel.assign(scope, res);
// indexModel.assign(scope.$parent, res);
});
});
}
}
}
]);
If I understand your code here, you are having a similar issue to what I answered here: Directive doesn't work when I which the version of Angular to 1.0.1 to 1.2.27.
You have created an Element Directive, called glob. The Directive has an Isolate Scope, which you attach a property, result in your example. This all works fine. The problem is, the property in the isolate scope is only accessible within the directive; and in your case, you are trying to access it outside the directive.
The Element <glob></glob> is your directive. This Element can be containers for other Elements, for example an angular expression {{result}} but these Elements are not part of the Directive, and therefore not scoped in the isolate.
If you were to include a template, and place {{result}} inside the template, you would see the expected result. However, this stops working if you change the variable you are passing in.
A rough draft of a working Directive using a transclude function might be something like:
.directive('glob', ['$parse', 'api', function($parse, $api) {
return {
priority: 99, // it needs to run after the attributes are interpolated
restrict: 'AE',
scope: {
},
transclude : true,
link: function(scope, iElement, iAttributes, ctrl, transclude) {
var indexModel = $parse(iAttributes.files);
iAttributes.$observe('matching', function(value) {
if (!value)
return;
$api.glob(value).then(function(res) {
indexModel.assign(scope, res);
// indexModel.assign(scope.$parent, res);
});
//append our scope into the DOM element (clone) instead of $scope
transclude(scope, function(clone, scope){
element.append(clone);
});
});
}
}
}
]);

Call method on Directive to pass data to Controller

So basically I have a controller, which lists a bunch of items.
Each item is rendering a directive.
Each directive has the ability to make a selection.
What I want to achieve is once the selection has been made, I want to call a method on the controller to pass in the selection.
What I have so far is along the lines of...
app.directive('searchFilterLookup', ['SearchFilterService', function (SearchFilterService) {
return {
restrict: 'A',
templateUrl: '/Areas/Library/Content/js/views/search-filter-lookup.html',
replace: true,
scope: {
model: '=',
setCriteria: '&'
},
controller: function($scope) {
$scope.showOptions = false;
$scope.selection = [];
$scope.options = [];
$scope.selectOption = function(option) {
$scope.selection.push(option);
$scope.setCriteria(option);
};
}
};
}]);
The directive is used like this:
<div search-filter-lookup model="customField" criteria="updateCriteria(criteria)"></div>
Then the controller has a function defined:
$scope.updateCriteria = function(criteria) {
console.log("Weeeee");
console.log(criteria);
};
The function gets called fine. But I'm unable to pass data to it :(
Try this:
$scope.setCriteria({criteria: option});
When you declare an isolated scope "&" property, angular parses the expression to a function that would be evaluated against the parent scope.
when invoking this function you can pass a locals object which extends the parent scope.
It's a common mistake to think that $scope.setCriteria is the same as the function inside the attribute. If you log it you'll see it's just an angular parsed expression function which have the parent scope saved at it's closure.
So when you run $scope.setCriteria() you actually evaluate an expression against the parent scope.
In your case this expression happens to be a function but it could be any expression.
But you don't have a criteria property on the parent scope, that's why angular let you pass a locals object to extend the parent scope. e.g. {criteria: option}
Extends the parent scope
you wrote in a comment that it requires the directive to have knowledge of the parameter name defined in the controller. No it doesn't, it just extends the parent scope with a criteria option, you can still use any expression you want though you are provided with an extra property you may use.
A good example would be ngEvents, take ng-click="doSomething($event)":
ngClick provides you with a local property $event, you don't have to use but you may if you need.
the directive doesn't know anything about the controller, it's up to you to decide which expression you write, cheers.
You can pass the function in using =...
scope: {
model: '=',
setCriteria: '='
},
controller: function($scope) {
// ...
$scope.selectOption = function(option) {
$scope.selection.push(option);
$scope.setCriteria(option);
};
}
<div search-filter-lookup model="customField" criteria="updateCriteria"></div>

Why doesn't scope pass through properly to nested directives?

In this example plunker, http://plnkr.co/edit/k2MGtyFnPwctChihf3M7, the nested directives compile fine when calculating the DOM layout, but error when the directive tries to reference a variable to bind to and says the variable is undefined. Why does this happen? The data model I am using is a single model for many nested directives so I want all nested directives to be able to edit the top level model.
I havn'et got a clue as to what you're trying to do. However, your comment 'so I want all nested directives to be able to edit the top level model' indicates you want your directive to have scope of your controller. Use
transclude = true
in your directive so that your directives can have access to your the parent scope.
http://docs.angularjs.org/guide/directive#creating-a-directive-that-wraps-other-elements
I don't know why you are doing it this way exactly, it seems like there should be a better way, but here goes a stab at getting your code working. First you create an isolated scope, so the scopes don't inherit or have access to anything but what is passed in the data attribute. Note that you can have your controller set dumbdata = ... and say <div data="dumbdata" and you will only have a data property on your isolated scope with the values from dumbdata from the parent in the data property. I usually try to use different names for the attribute and the data I'm passing to avoid confusion.
app.directive('project', function($compile) {
return {
template: '<div data="data"></div>',
replace: true,
scope: {
data: '=' // problem
},
Next, when you compile you are passing variables as scopes. You need to use real angular scopes. One way is to set scope: true on your directive definition, that will create a new child scope, but it will inherit from the parent.
app.directive('outer', function($compile) {
var r = {
restrict: 'A',
scope: true, // new child scope inherits from parent
compile: function compile(tEle, tAttr) {
A better way is probably to create the new child scope yourself with scope.$new(), and then you can add new child properties to pass for the descendants, avoiding the problem of passing values as scopes and still letting you have access to the individual values you're looping over (plunk):
app.directive('outer', function($compile) {
var r = {
restrict: 'A',
compile: function compile(tEle, tAttr) {
return function postLink(scope,ele,attrs) {
angular.forEach(scope.outer.middles, function(v, i) {
var x = angular.element('<div middle></div>');
var s = scope.$new(); // new child scope
s.middle = v; // value to be used by child directive
var y = $compile(x)(s); // compile using real angular scope
ele.append(y);
});
};
}
};
return r;
});

Resources