how to set an interpolated value in angular directive? - angularjs

How do I set the interpolated value in a directive? I can read the correct value from the following code, but I have not been able to set it.
js:
app.directive('ngMyDirective', function () {
return function(scope, element, attrs) {
console.log(scope.$eval(attrs.ngMyDirective));
//set the interpolated attrs.ngMyDirective value somehow!!!
}
});
html:
<div ng-my-directive="myscopevalue"></div>
where myscopevalue is a value on my controller's scope.

Whenever a directive does not use an isolate scope and you specify a scope property using an attribute, and you want to change that property's value, I suggest using $parse. (I think the syntax is nicer than $eval's.)
app.directive('ngMyDirective', function ($parse) {
return function(scope, element, attrs) {
var model = $parse(attrs.ngMyDirective);
console.log(model(scope));
model.assign(scope,'Anton');
console.log(model(scope));
}
});
fiddle
$parse works whether or not the attribute contains a dot.

If you want to set a value on the scope but don't know the name of the property (ahead of time), you can use object[property] syntax:
scope[attrs.myNgDirective] = 'newValue';
If the string in the attribute contains a dot (e.g. myObject.myProperty), this won't work; you can use $eval to do an assignment:
// like calling "myscopevalue = 'newValue'"
scope.$eval(attrs.myNgDirective + " = 'newValue'");
[Update: You should really use $parse instead of $eval. See Mark's answer.]
If you're using an isolate scope, you can use the = annotation:
app.directive('ngMyDirective', function () {
return {
scope: {
theValue: '=ngMyDirective'
},
link: function(scope, element, attrs) {
// will automatically change parent scope value
// associated by the variable name given to `attrs.ngMyDirective`
scope.theValue = 'newValue';
}
}
});
You can see an example of this in this Angular/jQuery color picker JSFiddle example, where assigning to scope.color inside the directive automatically updates the variable passed into the directive on the controller's scope.

Related

Angularjs: how to read object in Directive

I have passed an object from controller to directive but when i am reading object in directive i am not able to, it seems in directive object is being read as string.Code is below , i wane to read City and State from the object.
Html File
<div ng-controller="WeatherController">
<div weather ng-object="{{Object}}"></div>
</div>
Controller
.controller('WeatherController', ['$scope', function ($scope) {
$scope.webpartData.OverviewData.Person.Address.City;
$scope.Object = {
City: '',
State: ''
};
$scope.Object.City = 'TestCity';
$scope.Object.State = 'TestState';
});
})
}])
Directive
angular.module('WeatherModule', [])
.directive('Weather', ["$timeout", function($timeout) {
return {
restrict: 'EA',
template: '<div id="weather"></div>',
scope: {
ngObject: '#ngObject'
},
link: function(scope, element, attrs) {
scope.$watch('ngObject', function(value) {
scope.ngObject = value;
});
$timeout(function() {
console.log('Location' + scope.Object.City + ',' + scope.Object.State);
}, 100);
}
};
}])
There are differences between #, = and & in referencing the directive scope members.
1. "#" (Text binding / one-way binding)
2. "=" (Direct model binding / two-way binding)
3. "&" (Behaviour binding / Method binding)
# means that the changes from the controller scope will be reflected in the directive scope but if you modify the value in the directive scope, the controller scope variable will not get affected.
# always expects the mapped attribute to be an expression. This is very important; because to make the “#” prefix work, we need to wrap the attribute value inside {{}}.
= is birectional so if you change the variable in directive scope, the controller scope variable gets affected as well
& is used to bind controller scope method so that if needed we can call it from the directive
In your case you may need to use = instead of #
Have a look on the following fiddle (It's not mine, but it have good and to the point illustration) http://jsfiddle.net/maxisam/QrCXh/
Some Related Questions also:
What is the difference between '#' and '=' in directive scope in AngularJS?
What is the difference between & vs # and = in angularJS
You should use "=" instead "#". And ng-object="Object" instead ng-object="{{Object}}".

Is 'IsolateScope' in Angularjs using $watch by default?

I don't understand why or why not angularjs isolated scope use or not $watch?
For example:
app.directive('fooDirective', function () {
return {
scope: {
readonly: '=' or '#' or '&'
},
link: function (scope, element, attrs) {
// should I use $watch here or not ?
scope.$watch('readonly', function () {
// do I require to do so???
});
}
};
});
If you have HTML like this
<div foo-directive readonly="question.readonly">
The the following happens: scope.readonly within your directive gets the value of question.readonly (from outside the isolated scope). Whenever the value of question.readonly changes, the value of scope.readonly changes accordingly. You have nothing to do for that to happen.
But if you want to do something additionally whenever scope.readonly changes, like changing the color of an element when it's no longer read-only, the you need your own watcher (scope.$watch).
Isolated scopes and $watch is not the same. Using an isolated scope like
scope: {
myAttr: '='
}
tells $compile to bind to the my-attr="" attribute. That means if you change the value in your directive it gets updated in the parent scope(s) as well.
On the other hand, using $watch triggers a function if that value changes.

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);
});
});
}
}
}
]);

Unable to get the resolved attributes within custom directive

I have been trying to write a custom directive for an input field with dynamic id, in the directive am unable to get the correct id.
<input id="myInput{{$index}}" my-dir="fn()"/>
myApp.directive('myDir', function ($parse) {
var obj = {
require: "ngModel",
link: {
post: function (scope, element, attrs) {
var fn = $parse(attrs.myDir);
var elementId = element.attr('id');
console.log(elementId); // Here I see myInput{{$index}} instead of myInput0, by this time angular is not resolving the value
}
}
};
return obj;
});
My question would be, how can I get the resolved value in the directive. Also I cannot use any isolated scope here due to other reasons.
Thanks in advance
You can 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.
post: function (scope, element, attrs) {
attrs.$observe('id', function (id) {
console.log(id)
})
}
If you only want to evaluate the value once in the link function, you can use $interpolate (remember to inject it into your directive):
console.log($interpolate(element.attr('id'))(scope));
However, since you are likely using ng-repeat (because of the use of $index) I prefer #sza's answer, since your list may change, hence you may need to react to changes to your list.

Adding a directive to an existing element

I am attemping to add the required directive to an element, at some point in the future.
In the example, its if the model field is dirty, then make the element required.
I have attempted to just set the required attribute (being a little optimistic)
I am now compiling and linking the element and attempting to replace the old elemenet with the new element.
My element just disappears from the page?
Am I going about this the right way?
app.directive('requiredIfDirty', function ($compile, $timeout) {
return {
restrict: "A",
require: // element must have ng-model attribute.
'ngModel',
link: // scope = the parent scope
// elem = the element the directive is on
// attr = a dictionary of attributes on the element
// ctrl = the controller for ngModel.
function (scope, elem, attr, ctrl) {
var unsubscribe = scope.$watch(attr.ngModel, function (oldValue, newValue) {
if(angular.isUndefined(oldValue)) {
return;
}
attr.$set("required", true);
$timeout(function () {
var newElement = $compile(elem)(scope);
elem.replaceWith(newElement);
}, 1);
unsubscribe();
});
}
};
});
You would have to use Transclusion in your directive. This would allow you to yank your content, append required to it and then compile that. This is a great tutorial that explains the basic concept: Egghead.io - AngularJS - Transclusion Basics
You dont actually need to do that. Angular actually has a directive ng-required
see
http://docs.angularjs.org/api/ng.directive:input.text
You can provide an expression into ng-required on any field that has ng-model and it will add the required validator to it based on the expression evaluating to true.
From the docs
ngRequired(optional) – {string=} – Adds required attribute and
required validation constraint to the element when the ngRequired
expression evaluates to true. Use ngRequired instead of required when
you want to data-bind to the required attribute.

Resources