how to access object property via isolate scope without two-way binding? - angularjs

I want to pass a product's id to a directive like so:
<widget product-id="product.id"></widget>
I prefer not to use curly braces:
<widget product-id="{{product.id}}"></widget>
because it's more verbose, and I want to match the style of ng-model usage.
I want to use isolate scope so that I can't accidentally modify product.id inside the widget.
If I use:
scope {
productId: '#'
}
Then in my directive template: {{productId}} gives me the string "product.id"
If I use:
scope {
productId: '&'
}
I see no output in my directive template {{productId}}, and as I understand it & is for binding functions not properties.
If I use:
scope {
productId: '='
}
I get the exact value that I want (a number), but isn't this two-way binding and vulnerable to changing the product.id in the widget?

If you want to avoid the double curlys you can use $parse to process the Angular expression yourself:
Expressions are JavaScript-like code snippets that are usually placed
in bindings such as {{ expression }}. Expressions are processed by the
$parse service.
To use $parse in your directive you need to provide a scope (context) to parse against. Since you're using an isolated scope the value of product.id will be on the directive's parent's scope- so we'll use scope.$parent.
Here's an example:
myApp.directive('widget', function ($parse) {
return {
restrict: "E",
scope: {
productId: '#'
},
link: function(scope, element, attr) {
nvalue = $parse(scope.productId)(scope.$parent);
console.log("parse result",nvalue);
}
};
});
demo fiddle

I'm afraid there is no binding that fits your need, you can try this:
link:function(scope, element, attr){
scope.productId = scope.$parent.$eval(attr.productid);
}
But this approach is not recommended
DEMO
The recommended approach is using # binding and {{}} in your html
scope:{
productId: '#'
}
Your HTML:
<widget product-id="{{productId}}"></widget>
DEMO

Related

Angular: data not passing from controller to directive using isloated scope

The question title explain my problem i want to send data from a controller to directive so i can use the data in the directive controller or view.
Here is the controller code:
$scope.following = product.vendorId.isUserFollowing;
In the controller view:
<vas-follow following="{{following}}"></vas-follow>
following the property am trying to pass to the directive, the directive code:
.directive('vasFollow', vasFollow);
function vasFollow() {
var directive = {
restrict: "EA",
scope: {
following: '#'
},
link: link,
controller: vasFollowCtrl,
templateUrl: 'templates/directives/vasFollow.html',
};
return directive;
function link(scope, element, attrs) {
/* */
};
}
I tried first to use the following like so {{following}} in the directive view but it's not passing, also it is undefined in the directive controller.
I have read a lot of slimier issues but, i couldn't conclude why am having this problem.
Use ng-model for directive instead of your own replacement for it
Remove {{}} from assignment to share link to variable instead of just evaluated value
And please, use div or common DOM element instead of exact naming directive - it have side-effects in I.E.

Accessing ng-repeat scope on a custom directive

I'm having a go at a directive which will dynamically load a template based on a scope value passed into it.
I am using ng-repeat on my directive, and am using the iterated item as an attribute property:
<my-form-field ng-repeat="field in customFields" field="field">
In my directive I have the following code to set the template being used.
(function() {
'use strict';
angular
.module('app')
.directive('myFormField', myFormField);
function myFormField() {
var directive = {
restrict: 'E',
scope: {
field: '='
},
link: function(scope){
scope.getContentUrl = function() {
return 'app/modules/form_components/form_field/' + scope.field.type + '-template.html';
}
},
template: '<div ng-include="getContentUrl()"></div>'
};
return directive;
}
})();
Whilst the above works (which I found from other posts), I wonder if there is a better way.
For example I have seen examples of calling a function on the templateUrl config option instead, and then in that function access the scope attributes being passed in. When I tried this way, my field attribute was a literal 'field' string value (they are objects in my customFields array), so I think at that point the scope variables had not yet been evaluated.
With this current solution I am using, all of my templates get wrapped in an extra div since I am using ng-include, so I am just trying to make the rendered markup more succinct.
Any suggestions\advice is appreciated.
Thanks

angular directive attributes isolate scope undefined outside ng-repeat

I have directive this directive:
myApp.directive('myDirective',['$http', function($http) {
return {
restrict: 'AEC',
replace: true,
scope: { attr1: '=' , attr2: '=' },
link: function(scope, element, attrs) {
...
}
}]);
If I put directive inner ng-repeat it works, so I have acces to value of attributes (eg. scope.attr1)
<div ng-repeat="item in items"
<my-directive attr1="item.value1" attr2="item.value2"></my-directive>
</div>
but if I put directive outside ng-repeat, so I have only my-directive:
{{mymodel.value1}} {{mymodel.value2}} //{{}} print correct value
<my-directive attr1="mymodel.value1" attr2="mymodel.value2"></my-directive> //this fail
I can't access to attributes, so If I access eg. scope.attr1 I'm getting undefined value.
the "item" object is only defined in the div with the ngRepeat so when you are trying to access specific items from the list "items" outside of the div with ngRepeat you have to use items[index].value1 syntax. Try:
<my-directive attr1="items[index].value1" attr2="items[index].value2"></my-directive>
If the values are simple types, you should use '#' instead of the two-way model binding that '=' implies.
Also how are you reading your values. Do you read them through scope.attr1 or attrs['attr1'] - since the attr collection only holds the value in the attributes, whereas the scope actually lnks to the objects
There should be nothing wrong with your code. Maybe you could spot what you're doing differently:
Working Example Here

Custom directive in ng-repeat with isolated scope : loop items are suddenly null

I'm trying to use a custom directive in an ng-repeat loop. Without my custom directive the loop works fine: all items are displayed. But if I use my directive on the ng-repeat then all the items in the loop seem to be undefined or null, at least not printed.
Here is a simplified example:
http://jsfiddle.net/vtH64/13/
angular.module('myTest', []).directive('makecool', function(){
return {
scope: {
'flippity': '&'
},
link: function(scope, element){
element.append(", Yo!");
// do something with flippity
}
};
});
angular.module('myApp',['myTest']).controller('ListStuff', function($scope){
$scope.list = ["hi","there","this","be","a","list"];
});
It seems to have something to do with the isolated scope, because without the
scope: {
'flippity': '&'
},
which isolates the scope it works fine (http://jsfiddle.net/vtH64/15/), eventhough I will not be able to access 'flippity', which I need in the real world app.
What I am doing wrong here?
link method gets element's attributes as the third argument:
link: function(scope, element , attributes){
So you can get the flippity in a very easy way: attrs["flippity"]
Working fiddle: http://jsfiddle.net/vtH64/17/
Try with $parent if you are including the isolated scope...
<li ng-repeat="item in list" flippity="flop" makeCool>{{$parent.item}}</li>

Isolate scope attributes defined with # are undefined/disappear in directive's link function

The directive has isolate scope, and the scope attributes are being passed with "#".
This is how the directive is called:
<div ng-init="classForIcon = 'vladClass'"></div>
<div ng-init="textForIcon = 'Icon\'s text'"></div>
<div ng-init="routeForIcon = 'www.google.com'"></div>
<div ng-init="tooltipForIcon = 'my tooltip'"></div>
<div ng-init="imageForIcon = 'images/HOME_ICON1.png'"></div>
<rl-icon-widget icon-class="{{classForIcon}}" icon-text = "{{textForIcon}}" icon-tooltip="{{tooltipForIcon}}" icon-route="{{routeForIcon}}" icon-image="{{imageForIcon}}"></rl-icon-widget>
This is how the directive is defined:
'use strict';
fcPortalApp.directive('rlIconWidget', ['localizationAssistant', function(localizationAssistant) {
var obj = {
restrict: 'E',
templateUrl: 'scripts/directives/rliconwidget/rliconwidget.html',
//require: 'ngModel',
scope: {
//ngModel: '#',
iconClass: "#",
iconRoute: "#",
iconText: "#",
iconTooltip: "#",
iconImage: "#"
},
link: function(scope, element, attrs) {
console.log(scope);
console.log(scope.iconImage);
console.log(scope.iconTooltip);
console.log(scope.iconRoute);
}
};
console.log(obj);
return obj;
}]);
When I inspect the scope object (click on the output of console.log(scope_ in firebug), it looks like it has iconImage, iconTooltip and iconRoute properties set correctly.
Yet console.log(scope.iconImage), console.log(scope.iconTooltip) and console.log(scope.iconRoute) print "undefined"
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. -- directive doc
By the time you manually inspect the scope, the values get defined.
The reason we need to use $observe (actually $watch will also work for isolate scope properties defined with '#') is because a directive will likely need to do something whenever the interpolated value changes. E.g., if the value of textForIcon changes, you may want to modify something in the DOM that is managed by your directive.
If you need the values defined when the linking function runs, you have two options:
Use '=' instead of '#'. This will require that you remove the {{}}s from the HTML.
If the values won't change, pass strings: <rl-icon-widget icon-class="vladClass" ...> Then in your directive, simply use attrs.iconClass -- no need for '#'.

Resources