How do you pass an object reference to a directive? - javascript

In my app, I have an ng-repeat that iterate through JSON and prints each object to the page. So for example, my ng-repeat prints the animals
[
{
name: "horse",
sound: "Nay",
legs: 4,
},
{
name: "beaver",
sound: "thwack",
legs: 2
}
]
I also want to pass each animal to a directive and possibly add some key, values to them. The problem is, when I add the animal object as an attribute and update it in the directive,
i.e.
<animal this-animal={{animal}}></animal>
and in the directives link function
var animalObj = scope.$eval(attrs.thisAnimal);
animalObj["gestation"] = 10;
it doesn't update in the original JSON. It's like it gets disconnected from the overall array of all animals.
Why? How do I keep it all together? I want updates to individual objects to make changes in the main JSON object.

By using {{model}} in html it will resolve that value and place it into the HTML. In your case the JSON is getting stringified and then coverted back thus making a cloned object. Instead of using {{model}} just pass the name of the value.
<div my-directive="model">
Then access the model value using $parse
module.directive('myDirective', function($parse) {
return {
link: function(scope, element, attrs) {
var val = $parse(attrs.my directive)(scope);
}
};
});

you can use isolate scope. Assuming the array of animals is a scope property of the parent controller, you can do this:
<div ng-repeat="animal in animals">
<animal this-animal="animal"></animal>
</div>
And in the directive code:
module.directive('myDirective', function() {
return {
scope: {
thisAnimal: "="
},
link: function(scope, element, attrs) {
scope.thisAnimal.gestation = 10;
}
};
});
Refer the isolate scope section of this page for more details:
https://docs.angularjs.org/guide/directive

Related

Pass object to Angular directive's '&' parent scope function

How might one go about passing an object to Angular's (Angular 1.4.8) & ampersand scope binding directive?
I understand from the docs that there is a key-destructuring of sorts that needs named params in the callback function, and the parent scope uses these names as args. This SO answer gives a helpful example of the expected & functionality. I can get this to work when explicitly naming the params on the parent controller function call.
However, I am using the & to execute actions via a factory. The parent controller knows nothing of the params and simply hands the callback params to a dataFactory, which needs varied keys / values based on the action.
Once the promise resolves on the factory, the parent scope updates with the returned data.
As such, I need an object with n number of key / value pairs, rather than named parameters, as it will vary based on each configured action. Is this possible?
The closest I have seen is to inject $parse into the link function, which does not answer my question but is the sort of work-around that I am looking for. This unanswered question sounds exactly like what I need.
Also, I am trying to avoid encoding/decoding JSON, and I would like to avoid broadcast as well if possible. Code stripped down for brevity. Thanks...
Relevant Child Directive Code
function featureAction(){
return {
scope: true,
bindToController: {
actionConfig: "=",
actionName: "=",
callAction: "&"
},
restrict: 'EA',
controllerAs: "vm",
link: updateButtonParams,
controller: FeatureActionController
};
}
Child handler on the DOM
/***** navItem is from an ng-repeat,
which is where the variable configuration params come from *****/
ng-click="vm.takeAction(navItem)"
Relevant Child Controller
function FeatureActionController(modalService){
var vm = this;
vm.takeAction = takeAction;
function _callAction(params){
var obj = params || {};
vm.callAction({params: obj}); // BROKEN HERE --> TRYING
//TO SEND OBJ PARAMS
}
function executeOnUserConfirmation(func, config){
return vm.userConfirmation().result.then(function(response){ func(response, config); }, logDismissal);
}
function generateTasks(resp, params){
params.example_param_1 = vm.add_example_param_to_decorate_here;
_callAction(params);
}
function takeAction(params){
var func = generateTasks;
executeOnUserConfirmation(func, params);
}
Relevent Parent Controller
function callAction(params){
// logs undefined -- works if I switch to naming params as strings
console.log("INCOMING PARAMS FROM CHILD CONTROLLER", params)
executeAction(params);
}
function executeAction(params){
dataService.executeAction(params).then(function(data){
updateRecordsDisplay(data); });
}
I think the example below should give you enough of a start to figure out your question:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>Angular Callback</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var myApp = angular.module("myApp", []);
myApp.controller('appController', function($scope) {
$scope.var1 = 1;
$scope.handleAction1 = function(params) {
console.log('handleAction1 ------------------------------');
console.log('params', params);
}
$scope.handleAction2 = function(params, val1) {
console.log('handleAction2 ------------------------------');
console.log('params', params);
console.log('val1', val1);
}
});
myApp.controller('innerController', innerController);
innerController.$inject = ['$scope'];
function innerController($scope) {
$scope.doSomething = doSomething;
function doSomething() {
console.log('doSomething()');
var obj = {a:1,b:2,c:3}; // <-- Build your params here
$scope.callAction({val1: 1, params: obj});
}
}
myApp.directive('inner', innerDirective );
function innerDirective() {
return {
'restrict': 'E',
'template': '{{label}}: <button ng-click="doSomething()">Do Something</button><br/>',
'controller': 'innerController',
'scope': {
callAction: '&',
label: '#'
}
};
}
</script>
</head>
<body ng-controller="appController">
<inner label="One Param" call-action="handleAction1(params)"></inner>
<inner label="Two Params" call-action="handleAction2(params, val)"></inner>
</body>
</html>
In the appController I have two functions that will be called by the inner directive. The directive is expecting the outer controller to pass in those functions using the call-action attribute on the <inner> tag.
When you click on the button within the inner directive it called the function $scope.doSomething This, in turn calls to the outer controller function handleAction1 or handleAction2. It also passes a set of parameters val1 and params:
$scope.callAction({val1: 1, params: obj});
In your template you specify which of those parameters you want to be passed into your outer controller function:
call-action="handleAction1(params)"
or
call-action="handleAction2(params, val)"
Angular then uses those parameter names to look into the object you sent when you called $scope.callAction.
If you need other parameters passed into the outer controller function then just add then into the object defined in the call to $scope.callAction. In your case you would want to put more content into the object you pass in:
var obj = {a:1,b:2,c:3}; // <-- Build your params here
Make that fit your need and then in your outer controller you would take in params and it would be a copy of the object defined just above this paragraph.
It this is not what you were asking, let me know.

Angular Custom Directives: Calling a function with arguments from within the link function

I'm building a list of clickable options that is filtered by an input, using a custom directive.
HTML:
<combobox
input-model="myModel"
options="myList"
option-label="label"
option-select="selectFn()"></combobox>
The Directive (simplified):
app.directive("combobox", function() {
return {
restrict: "E",
replace: true,
template: "<input type=‘text’ ng-model=‘inputModel’ />" +
"<button ng-repeat='option in options | " +
"filter: inputModel’" +
"ng-mousedown=‘optionSelected(option)’" +
">{{option[optionLabel]}}</button>",
scope: {
inputModel: "=",
options: "=",
optionLabel: "#",
optionSelect: "&"
},
link: function(scope, elem, attrs) {
scope.optionSelected = function(option) {
// some stuff here...
scope.optionSelect();
}
}
}
})
Scope:
$scope.myList = [
{ label: "First Option", value: 1 },
{ label: "Second Option", value: 2 },
{ label: "Second Option", value: 2 }
]
$scope.selectFn() = function() {
// doing stuff here...
}
But I want to call selectFn with properties from the option that called it. Something like:
option-select=“selectFn(option.value)”
or
scope.optionSelect(option);
Is this possible? Can I call a function in scope and pass arguments from within the link function?
For customization reasons, I cannot use a combo box library, like ui-select.
You are passing the result of the function, evaluated in the parent scope, instead of the function itself. You can evaluate your expression and then execute the resulting function.
So, what you should try is this
<combobox
input-model="myModel"
options="myList"
option-label="label"
option-select="selectFn">
in your markup, and then in your directive
link: function(scope, elem, attrs) {
scope.optionSelected = function(option) {
// some stuff here...
scope.optionSelect()(option);
}
}
Notice that the expression in option-select="selectFn" will be handed to your isolate scope wrapped in the optionSelect function. When you evaluate it, you get the function you want. That's why you use scope.optionSelect()(option)
See your directive working here: http://plnkr.co/edit/zGymbiSYgnt4IJFfvJ6G
From https://docs.angularjs.org/api/ng/service/$compile
& or &attr - provides a way to execute an expression in the context of
the parent scope. If no attr name is specified then the attribute name
is assumed to be the same as the local name. Given and widget definition of scope: {
localFn:'&myAttr' }, then isolate scope property localFn will point to
a function wrapper for the count = count + value expression. Often
it's desirable to pass data from the isolated scope via an expression
to the parent scope, this can be done by passing a map of local
variable names and values into the expression wrapper fn. For example,
if the expression is increment(amount) then we can specify the amount
value by calling the localFn as localFn({amount: 22})
You should call it like this inside your directive:
scope.optionSelect({
data: []
});
Your template (object will be the object with the data array):
option-select="functionToCall(object)"
Then in your controller:
$scope.functionToCall = function(object){
console.log(object);
//will output: []
}

Angular JS: How do I set a property on directive local scope that i can use in the template?

I want to access a variable in this case map in the directive without having to predefine it like setting it as an attr of the directrive map="someValue". And also i dont want to use scope.$apply because i actually only want the variable in the isolated scope of the directive. Is this even possible ?
What is the best practice here? Basically my directive needs to do both. Access the parent scope and have its own scope which with i can build the template with.
Thank you everybody.
Here my Js code:
.directive('myFilter', function() {
return {
restrict: 'E',
scope: {
source: '=source',
key: '=key',
},
link: function(scope, element, attrs) {
scope.$on('dataLoaded', function(e) {
scope.map = {};
angular.forEach(scope.source, function(paramObj) {
if (!scope.map[paramObj[scope.key]]) {
var newEntry = {
value: paramObj[scope.key],
isChecked: false
}
scope.map[paramObj[scope.key]] = newEntry;
}
});
});
}
}
});
and my html:
<my-filter source="metaPara.filteredParameters" key="'value'">
<table class="table table-borered">
<tr data-ng-repeat="item in map">
<td>{{item.value}}</td>
</tr>
</table>
</my-filter>
You might want to refer to the Angular documentation for directives, again.
If you want an isolate-scope (a scope which has no access to ancestors), then use
scope : { /* ... */ }
otherwise, if you want a unique scope, which does have access to ancestors, use
scope : true
Then, you can put your HTML-modifying or event-listening (that doesn't rely on ng-click or something else Angular already covers) in
link : function (scope, el, attrs, controller) { }
...and you can put all of your regular implementation inside of
controller : ["$scope", function ($scope) {
var myController = this;
myController.property = "12";
}],
controllerAs : "myController"
So that in your template you can say:
<span>{{ myController.property }}</span>
You can also use a pre-registered controller, which you call by name:
controller : "mySimpleController",
controllerAs : "myController"
Also, rather than using $scope.$apply, I'd recommend using $timeout (has to be injected).
The difference is that $scope.$apply will only work at certain points -- if you're already inside of a digest cycle, it will throw an error, and not update anything.
$timeout( ) sets the updates to happen during the next update-cycle.
Ideally, you should know whether or not you need an $apply or not, and be able to guarantee that you're only using it in one spot, per update/digest, but $timeout will save you from those points where you aren't necessarily sure.

How to Create recursive Angular.js Templates without isolating scope?

I have a recursive data structure I am trying to represent in Angular.js. a simplified demo is available here:
http://plnkr.co/edit/vsUHLYMfI4okbiVlCK7O?p=preview
In the Preview, I have the following HTML for a recursive object:
<ul>
<li ng-repeat="person in people">
<span ng-click="updateClicks(person)">{{person.name}}</span>
<ul>
<li ng-repeat="kid in person.kids">
<span ng-click="updateClicks(kid)">{{kid.name}}</span>
</li>
</ul>
</li>
</ul>
In my application, the view is much more complex. I would like to have a way to generate the template html for each person in a recursive fashion. I tried doing this with a directive, however I ran into issues with infinite loops when I did not isolate the scope. And when I did isolate the scope, I was no longer able to call functions that are tied to the controller (in this example, the updateClicks function, however in my application there are several).
How can I generate html for these objects recursively, and still be able to call functions belonging to a controller?
I think the best way to do this is with an $emit.
Let's say your recursive directive looks like this:
directive('person', function($compile){
return{
restrict: 'A',
link: function(scope, element, attributes){
//recursive bit, if we've got kids, compile & append them on
if(scope.person.kids && angular.isArray(scope.person.kids)) {
$compile('<ul><li ng-repeat="kid in person.kids" person="kid"></li></ul>')(scope, function(cloned, scope){
element.find('li').append(cloned);
});
}
},
scope:{
person:'='
},
template: '<li><span ng-click="$emit(\'clicked\', person)">{{person.name}}</span></li>'
}
});
notice the ng-click="$emit(clicked, person)" code, don't be distracted the \, that's just there to escape. $scope.$emit will send an event all the way up your scope chain, so that in your controller, your clicked function stays mostly unchanged, but now instead of being triggered by ng-click, you're listening for the event.
$scope.$on('clicked', function(event, person){
person.clicks++;
alert(person.name + ' has ' + person.clicks + ' clicks!');
});
cool thing is that the event object even has the isolated scopes from your recursed directives.
Here's the fully working plnkr: http://plnkr.co/edit/3z8OXOeB5FhWp9XAW58G?p=preview
even went down to tertiary level to make sure recursion was working.
Recursive tree with angular directive without scope isolation, forces you to simulate isolation by using different scope properties per depth level.
I didn't find any so I wrote my own.
Let's say your HTML is :
<body ng-app="App" ng-controller="AppCtrl">
<div test="tree.children" test-label="tree.label">{{b}}</div>
</body>
Then you have a main module and a controller adding a tree to the scope :
var App = angular.module('App', []);
App.controller('AppCtrl', function($scope, $timeout) {
// prodive a simple tree
$scope.tree = {
label: 'A',
children: [
{
label: 'a',
children: [
{ label: '1' },
{ label: '2' }
]
},
{
label: 'b',
children: [
{ label: '1' },
{ label: '2' }
]
}
]
};
// test that pushing a child in the tree is ok
$timeout(function() {
$scope.tree.children[1].children.push({label: 'c'});
},2000);
$timeout(function() {
// test that changing a label is ok
$scope.tree.children[1].label = 'newLabel';
},4000);
});
Finally consider the following implementation of the directive test :
App.directive('test', function($compile) {
// use an int to suffix scope properties
// so that inheritance does not cause infinite loops anymore
var inc = 0;
return {
restrict: 'A',
compile: function(element, attr) {
// prepare property names
var prop = 'test'+(++inc),
childrenProp = 'children_'+prop,
labelProp = 'label'+prop,
childProp = 'child_'+prop;
return function(scope, element, attr) {
// create a child scope
var childScope = scope.$new();
function observeParams() {
// eval attributes in current scope
// and generate html depending on the type
var iTest = scope.$eval(attr.test),
iLabel = scope.$eval(attr.testLabel),
html = typeof iTest === 'object' ?
'<div>{{'+labelProp+'}}<ul><li ng-repeat="'+childProp+' in '+childrenProp+'"><div test="'+childProp+'.children" test-label="'+childProp+'.label">{{'+childProp+'}}</div></li></ul></div>'
: '<div>{{'+labelProp+'}}</div>';
// set scope values and references
childScope[childrenProp]= iTest;
childScope[labelProp]= iLabel;
// fill html
element.html(html);
// compile the new content againts child scope
$compile(element.contents())(childScope);
}
// set watchers
scope.$watch(attr.test, observeParams);
scope.$watch(attr.testLabel, observeParams);
};
}
};
});
All the explanations are in the comments.
You may have a look at the JSBin.
My implementation can of course be improved.

How to get evaluated attributes inside a custom directive

I'm trying to get an evaluated attribute from my custom directive, but I can't find the right way of doing it.
I've created this jsFiddle to elaborate.
<div ng-controller="MyCtrl">
<input my-directive value="123">
<input my-directive value="{{1+1}}">
</div>
myApp.directive('myDirective', function () {
return function (scope, element, attr) {
element.val("value = "+attr.value);
}
});
What am I missing?
Notice: I do update this answer as I find better solutions. I also keep the old answers for future reference as long as they remain related. Latest and best answer comes first.
Better answer:
Directives in angularjs are very powerful, but it takes time to comprehend which processes lie behind them.
While creating directives, angularjs allows you to create an isolated scope with some bindings to the parent scope. These bindings are specified by the attribute you attach the element in DOM and how you define scope property in the directive definition object.
There are 3 types of binding options which you can define in scope and you write those as prefixes related attribute.
angular.module("myApp", []).directive("myDirective", function () {
return {
restrict: "A",
scope: {
text: "#myText",
twoWayBind: "=myTwoWayBind",
oneWayBind: "&myOneWayBind"
}
};
}).controller("myController", function ($scope) {
$scope.foo = {name: "Umur"};
$scope.bar = "qwe";
});
HTML
<div ng-controller="myController">
<div my-directive my-text="hello {{ bar }}" my-two-way-bind="foo" my-one-way-bind="bar">
</div>
</div>
In that case, in the scope of directive (whether it's in linking function or controller), we can access these properties like this:
/* Directive scope */
in: $scope.text
out: "hello qwe"
// this would automatically update the changes of value in digest
// this is always string as dom attributes values are always strings
in: $scope.twoWayBind
out: {name:"Umur"}
// this would automatically update the changes of value in digest
// changes in this will be reflected in parent scope
// in directive's scope
in: $scope.twoWayBind.name = "John"
//in parent scope
in: $scope.foo.name
out: "John"
in: $scope.oneWayBind() // notice the function call, this binding is read only
out: "qwe"
// any changes here will not reflect in parent, as this only a getter .
"Still OK" Answer:
Since this answer got accepted, but has some issues, I'm going to update it to a better one. Apparently, $parse is a service which does not lie in properties of the current scope, which means it only takes angular expressions and cannot reach scope.
{{,}} expressions are compiled while angularjs initiating which means when we try to access them in our directives postlink method, they are already compiled. ({{1+1}} is 2 in directive already).
This is how you would want to use:
var myApp = angular.module('myApp',[]);
myApp.directive('myDirective', function ($parse) {
return function (scope, element, attr) {
element.val("value=" + $parse(attr.myDirective)(scope));
};
});
function MyCtrl($scope) {
$scope.aaa = 3432;
}​
.
<div ng-controller="MyCtrl">
<input my-directive="123">
<input my-directive="1+1">
<input my-directive="'1+1'">
<input my-directive="aaa">
</div>​​​​​​​​
One thing you should notice here is that, if you want set the value string, you should wrap it in quotes. (See 3rd input)
Here is the fiddle to play with: http://jsfiddle.net/neuTA/6/
Old Answer:
I'm not removing this for folks who can be misled like me, note that using $eval is perfectly fine the correct way to do it, but $parse has a different behavior, you probably won't need this to use in most of the cases.
The way to do it is, once again, using scope.$eval. Not only it compiles the angular expression, it has also access to the current scope's properties.
var myApp = angular.module('myApp',[]);
myApp.directive('myDirective', function () {
return function (scope, element, attr) {
element.val("value = "+ scope.$eval(attr.value));
}
});
function MyCtrl($scope) {
}​
What you are missing was $eval.
http://docs.angularjs.org/api/ng.$rootScope.Scope#$eval
Executes the expression on the current scope returning the result. Any exceptions in the expression are propagated (uncaught). This is useful when evaluating angular expressions.
For an attribute value that needs to be interpolated in a directive that is not using an isolated scope, e.g.,
<input my-directive value="{{1+1}}">
use Attributes' method $observe:
myApp.directive('myDirective', function () {
return function (scope, element, attr) {
attr.$observe('value', function(actual_value) {
element.val("value = "+ actual_value);
})
}
});
From the directive page,
observing interpolated attributes: Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined.
If the attribute value is just a constant, e.g.,
<input my-directive value="123">
you can use $eval if the value is a number or boolean, and you want the correct type:
return function (scope, element, attr) {
var number = scope.$eval(attr.value);
console.log(number, number + 1);
});
If the attribute value is a string constant, or you want the value to be string type in your directive, you can access it directly:
return function (scope, element, attr) {
var str = attr.value;
console.log(str, str + " more");
});
In your case, however, since you want to support interpolated values and constants, use $observe.
The other answers here are very much correct, and valuable. But sometimes you just want simple: to get a plain old parsed value at directive instantiation, without needing updates, and without messing with isolate scope. For instance, it can be handy to provide a declarative payload into your directive as an array or hash-object in the form:
my-directive-name="['string1', 'string2']"
In that case, you can cut to the chase and just use a nice basic angular.$eval(attr.attrName).
element.val("value = "+angular.$eval(attr.value));
Working Fiddle.
For the same solution I was looking for Angularjs directive with ng-Model.
Here is the code that resolve the problem.
myApp.directive('zipcodeformatter', function () {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.ngModel, function (v) {
if (v) {
console.log('value changed, new value is: ' + v + ' ' + v.length);
if (v.length > 5) {
var newzip = v.replace("-", '');
var str = newzip.substring(0, 5) + '-' + newzip.substring(5, newzip.length);
element.val(str);
} else {
element.val(v);
}
}
});
}
};
});
HTML DOM
<input maxlength="10" zipcodeformatter onkeypress="return isNumberKey(event)" placeholder="Zipcode" type="text" ng-readonly="!checked" name="zipcode" id="postal_code" class="form-control input-sm" ng-model="patient.shippingZipcode" required ng-required="true">
My Result is:
92108-2223
var myApp = angular.module('myApp',[]);
myApp .directive('myDirective', function ($timeout) {
return function (scope, element, attr) {
$timeout(function(){
element.val("value = "+attr.value);
});
}
});
function MyCtrl($scope) {
}
Use $timeout because directive call after dom load so your changes doesn`'t apply

Categories

Resources