There is one thing I don't understand with one-way bindings demonstrated by the code and demo below:
var example = {
bindings: {
obj: '<',
prim: '<'
},
template: `
<div class="section">
<h4>
Isolate Component
</h4>
<p>Object: {{ $ctrl.obj }}</p>
<p>Primitive: {{ $ctrl.prim }}</p>
<a href="" ng-click="$ctrl.updateValues();">
Change Isolate Values
</a>
</div>
`,
controller: function () {
this.updateValues = function () {
this.prim = 10;
this.obj = {
john: {
age: 35,
location: 'Unknown'
}
};
};
}
};
function ParentController() {
this.somePrimitive = 99;
this.someObject = {
todd: {
age: 25,
location: 'England, UK'
}
};
this.updateValues = function () {
this.somePrimitive = 33;
this.someObject = {
jilles: {
age: 20,
location: 'Netherlands'
}
};
};
}
angular
.module('app', [])
.component('example', example)
.controller('ParentController', ParentController);
DEMO
The thing I don't understand is how the primitive value changes (or doesn't change) if you modify it in the isolated scope. As far as I understand if you one-way bind a primitive, the binding will break if you change that value within the isolated scope. With break I mean that an update by the parent will not propagate anymore into the isolated scope.
However, in the demo it doesn't exactly work like that.
HOW TO REPRODUCE: Go to the DEMO and first click the Change isolate values button. This will change the primitive in the isolated scope. Next click the Change Parent Values button. As you can see the primitive within the isolated scope is still updated. Based on this, I guess the binding doesn't break if you update a primitive inside the isolated scope, right?! But now things get weird. If you repeat this, you'll notice that this time the binding doesn't work anymore.
Can someone explain why the primitive value did change the first time but didn't the second time ?
UPDATE: Found the solution. The binding works perfect, because the value of the parent doesn't change anymore, its always 10. Here is an updated example which shows that it still works
Related
in the new project I'm working on I've started using the components instead of directives.
however, I've encountered an issue where I cannot find a concrete standard way to do it.
It's easy to notify an event from child to parent, you can find it on my plunkr below, but what's the correct way to notify a event from parent to child?
Angular2 seems to solve this issue by using something like this: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-child-local-var
But I don't tink there's a possibilty to define a "pointer" to the child component like the example did with #timer
In order to mantain a possible easy conversion to Angular2 I want to avoid:
event emitting (emit and broadcast from the scopes)
using the require from the child (and then add a callback to the parent..UGLY)
using a one-way binding, injecting the scope in the child and then "watch" this property.. MORE UGLY
Example code:
var app = angular.module('plunker', []);
app.controller('RootController', function() {
});
app.component('parentComponent', {
template: `
<h3>Parent component</h3>
<a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Notify Child</a>
<span data-ng-bind="$ctrl.childMessage"></span>
<child-component on-change="$ctrl.notifiedFromChild(count)"></child-component>
`,
controller: function() {
var ctrl = this;
ctrl.notifiedFromChild = function(count){
ctrl.childMessage = "From child " + count;
}
ctrl.click = function(){
}
},
bindings: {
}
});
app.component('childComponent', {
template: `
<h4>Child component</h4>
<a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Notify Parent</a>
`,
controller: function() {
var ctrl = this;
ctrl.counter = 0;
ctrl.click = function(){
ctrl.onChange({ count: ++ctrl.counter });
}
},
bindings: {
onChange: '&'
}
});
You can find an example here:
http://plnkr.co/edit/SCK8XlYoYCRceCP7q2Rn?p=preview
This is a possible solution I created
http://plnkr.co/edit/OfANmt4zLyPG2SZyVNLr?p=preview
where the child requires the parent, and then child sets a parent reference to the child... now parent can use the child... ugly but it's like angular2 example above
Communicating Events from Parent to Child in AngularJS Components
Publish Directive $API Using Expression Binding
To allow parent components to communicate events to a child component, have the child publish an API:
<grid-component grid-on-init="$ctrl.gridApi=$API; $ctrl.someFn($API)">
</grid-component>
JS
app.component('gridComponent', {
//Create API binding
bindings: {gridOnInit: "&"},
template: `
<h4>Grid component</h4>
<p> Save count = {{$ctrl.count}}</p>
`,
controller: function() {
var ctrl = this;
this.$onInit = function() {
ctrl.count = 0;
ctrl.api = {};
//Publish save function
ctrl.api.save = save;
//Invoke Expression with $API as local
ctrl.gridOnInit({$API: ctrl.api});
};
function save(){
console.log("saved!");
ctrl.count++;
}
}
});
The above example invokes the Angular Expression defined by the grid-on-init attribute with its API exposed as $API. The advantage to this approach is that the parent can react to child initialization by passing a function to the child component with the Angular Expression.
From the Docs:
The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the directive's element. These local properties are useful for aliasing values for templates. The keys in the object hash map to the name of the property on the isolate scope; the values define how the property is bound to the parent scope, via matching attributes on the directive's element:
& 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 <my-component my-attr="count = count + value"> and the isolate scope definition scope: { localFn:'&myAttr' }, the 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}).
-- AngularJS Comprehensive Directive API -- scope
As a convention, I recommend prefixing local variables with $ to distinguish them from parent variables.
Alternately use Bi-Directional Binding
NOTE: To ease the transition to Angular 2+, avoid the use of bi-directional = binding. Instead use one-way < binding and expression & binding. For more information, see AngularJS Developer Guide - Understanding Components.
To allow parent components to communicate events to a child component, have the child publish an API:
<grid-component api="$ctrl.gridApi"></grid-component>
In the above example, the grid-component uses bindings to publish its API onto the parent scope using the api attribute.
app.component('gridComponent', {
//Create API binding
bindings: {api: "="},
template: `
<h4>Grid component</h4>
<p> Save count = {{$ctrl.count}}</p>
`,
controller: function() {
var ctrl = this;
this.$onInit = function() {
ctrl.count = 0;
ctrl.api = {};
//Publish save function
ctrl.api.save = save;
};
function save(){
console.log("saved!");
ctrl.count++;
}
}
});
Then the parent component can invoke the child save function using the published API:
ctrl.click = function(){
console.log("Search clicked");
ctrl.gridApi.save();
}
The DEMO on PLNKR.
Here is an easy way: http://morrisdev.com/2017/03/triggering-events-in-a-child-component-in-angular/
basically, you add a bound variable called "command" (or whatever you want) and use the $onChanges to pay attention to changes of that variable and trigger whatever event it says to trigger manually.
I personally like to put all my variables into an object called "settings" and send that to all my components. However, a change to a value within an object does NOT trigger the $onChanges event, so you NEED to tell it to trigger the event with a flat variable.
I'd say it is not the "proper" way to do it, but it sure is a lot easier to program, a lot easier to understand, and a lot easier to convert to A2 later on down the road.
I faced with same question. What do you think about this approach: to use inheritance via require instead of Bi-Directional Binding?
http://plnkr.co/edit/fD1qho3eoLoEnlvMzzbw?p=preview
var app = angular.module('plunker', []);
app.controller('RootController', function() {
});
app.component('filterComponent', {
template: `
<h3>Filter component</h3>
<a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Search</a>
<span data-ng-bind="$ctrl.childMessage"></span>
<grid-component api="$ctrl.gridApi"></grid-component>
`,
controller: function() {
var ctrl = this;
ctrl.click = function(){
console.log("Search clicked");
ctrl.gridApi.save();
};
}
});
app.component('gridComponent', {
require: {parent:'^^filterComponent'},
bindings: {api: "<"},
template: `
<h4>Grid component</h4>
<p> Save count = {{$ctrl.count}}
`,
controller: function() {
var ctrl = this;
this.$onInit = function() {
ctrl.count = 0;
ctrl.api = {};
ctrl.api.save = save;
ctrl.parent.gridApi = ctrl.api;
};
function save(){
console.log("saved!");
ctrl.count++;
}
}
});
Or we can define setter method for parent to make it more explicit.
http://plnkr.co/edit/jmETwGt32BIn3Tl0yDzY?p=preview
var app = angular.module('plunker', []);
app.controller('RootController', function() {
});
app.component('filterComponent', {
template: `
<h3>Filter component</h3>
<a class="btn btn-default btn-sm" ng-click="$ctrl.click()">Search</a>
<span data-ng-bind="$ctrl.childMessage"></span>
<grid-component pass-api="$ctrl.setGridApi(api)"></grid-component>
`,
controller: function() {
var ctrl = this;
var gridApi = {};
ctrl.setGridApi = function(api){
gridApi = api;
};
ctrl.click = function(){
console.log("Search clicked");
gridApi.save();
};
}
});
app.component('gridComponent', {
bindings: {
passApi:'&'
},
template: `
<h4>Grid component</h4>
<p> Save count = {{$ctrl.count}}
`,
controller: function() {
var ctrl = this;
this.$onInit = function() {
ctrl.count = 0;
ctrl.api = {};
ctrl.api.save = save;
ctrl.passApi({api: ctrl.api});
};
function save(){
console.log("saved!");
ctrl.count++;
}
}
});
SIMPLE: You just need one property 1 way bound because 2 way binding only calls onChanges at creation.
Set a new boolean property on the parent controller.
vm.changeNow = false;
//update this to vm.changeNow = !vm.changeNow when you want to tell the component to
//call a method.
Open Child Component, in the bindings section,
bindings: {
a2waybind: '=',
changenow: '<'
}
You now need an $onChanges event on the child.
$onChanges() {
// do that sweet stuff you wanted to listen from the parent for.
}
Now when calling the template:
childComponent a2waybind="$ctrl.mycoolvalue" changenow="$ctrl.changeNow" /childComponent"
A second way to do this is in your child component:
var vm = this;
var myprop;
Object.defineProperty(vm, 'mytwowayprop', {
get() {
return myprop;
},
set(value) {
myprop = value;
vm.onchangeseventbecausemypropchanged();
}
});
vm.onchangeseventbecausemypropchanged = function () {//woot}
This will allow you to have a defined onchanges event when your two way binding property changes both internally and externally.
This is kind of a weird question, but here's the idea:
Let's say I have a complex JSON object coming back from an HTTP call and attaching to the $scope. Something like this:
$scope.obj = {
user: {
id: 10,
name: { first: 'Joe', last: 'Smith' },
contact: {
home: {
street: '101 First St.',
city: 'Myville',
state: 'Jokelahoma',
zip: '98765'
},
email: 'joeshmoe#gmail.com',
phone: '+12345678901'
}
},
purchase_hist: [
{ item_id: 11004, date: 'Thu, 06 Aug 2015 13:51:17 GMT' },
{ item_id: 97020, date: 'Fri, 31 Jul 2015 18:57:57 GMT' }
]
}
Now, if I wanted to display an overview of purchase history in an AngularJS partial, I could do something like this:
<table>
<tr ng-repeat="p in obj.purchase_hist">
<td>{{p.item_id}}</td>
<td>{{p.date}}</td>
</tr>
</table>
The really convenient thing about this format (though it's not super evident here with so few props) is that the purchase being described is aliased as p. I don't have to do obj.purchase_hist[0].item_id, I can just do p.item_id.
But what about when I go to show the user's home address? Do I really have to do this?:
<div>
<p>{{obj.user.contact.home.street}}</p>
<p>{{obj.user.contact.home.city}}</p>
<p>{{obj.user.contact.home.state}}</p>
<p>{{obj.user.contact.home.zip}}</p>
</div>
That's really verbose. I would much rather use something akin to the controller as ... syntax, something like this:
<div ng-alias="obj.user.contact.home as uhome">
<p>{{uhome.street}}</p>
<p>{{uhome.city}}</p>
<p>{{uhome.state}}</p>
<p>{{uhome.zip}}</p>
</div>
Is there such a thing that exists in AngularJS ? Unfortunately I'm not very able to use plugins in my environment, so I'm specifically looking for a part of angular core that will work this way.
Thanks!
I've written this little directive, which allow you to perform what you want :
Directive ngAlias
(function(){
function ngAlias($compile) {
return {
restrict: "A",
link: function(scope, element, attrs) {
var args = attrs.ngAlias.split('as').map(function(elm){return elm.replace(/ /g,'')});
scope[args[0]] = '';
var dot = args[1].split('.');
var object = {};
dot.forEach(function(value, index){
index === 0
? object = scope[value]
: object = object[value] === null ? object[value] = {} : object[value];
});
console.log(object)
scope[args[0]] = object;
}
};
}
angular
.module('app')
.directive('ngAlias', ngAlias);
})();
For example, set your object in your controller
Controller
(function(){
function Controller($scope) {
$scope.obj = {
toto: {
nom: 'toto',
prenom: 'tata'
}
};
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
And you can use it :
HTML
<body ng-app="app" ng-controller="ctrl">
<div ng-alias="toto as obj.toto">
{{toto.nom}}
</div>
</body>
#PaulBoutes provided the answer I needed, and he should get the credit; I just wanted to add the version of the directive that I settled on based on his answer.
app.directive('alias', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var splits = attrs['alias'].trim().split(/\s+as\s+/);
scope.$watch(splits[0], function(val) {
scope.$eval(splits[1]+'=('+splits[0]+')');
});
}
};
});
Same basic idea as Paul's, just cleaned up a little and made a little more flexible in terms of whitespace and such.
Usage example:
<div data-alias="obj.user.contact.home as uhome">
<p>{{uhome.street}}</p>
<p>{{uhome.city}}</p>
<p>{{uhome.state}}</p>
<p>{{uhome.zip}}</p>
</div>
Certainly there's no shortage of workable solutions here, but there shouldn't be harm in adding another.
I threw this together the other day to save myself some typing and make my html easier to read. It creates a new child scope, so it doesn't pollute the existing scope with these aliases. It simply works by binding the parent scope's property to the new child scope with whatever name you specify.
app.directive('with', function() {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
if (attrs.as) {
scope[attrs.as] = scope.$eval(attrs.with);
} else {
angular.extend(scope, scope.$eval(attrs.with));
}
}
};
});
So, instead of doing this:
<div>{{some.deeply.nested.model.id}} - {{some.deeply.nested.model.name}}</div>
Do this:
<div with="some.deeply.nested.model" as="t">{{t.id}} - {{t.name}}</div>
Or, do it without using as:
<div with="some.deeply.nested.model">{{id}} - {{name}}</div>
I came across this just now and tested out the ngAlias directive, which may or may not work fine, I don't know because I had issues with it, probably due to some other unrelated error, and I decided on this simpler method:
<div ng-repeat="uhome in [obj.user.contact.home]">
<p>{{uhome.street}}</p>
<p>{{uhome.city}}</p>
<p>{{uhome.state}}</p>
<p>{{uhome.zip}}</p>
</div>
Tada! "obj.user.contact.home" is now aliased to "uhome". I believe this solution is actually exactly what you wanted when you asked this question, too, because it literally uses ng-repeat and therefore has all the benefits of ng-repeat's built-in aliasing mechanism.
<div ng-init="uhome = obj.user.contact.home">
<p>{{uhome.street}}</p>
<p>{{uhome.city}}</p>
<p>{{uhome.state}}</p>
<p>{{uhome.zip}}</p>
</div>
https://code.angularjs.org/1.3.16/docs/api/ng/directive/ngInit
As far as i know there is nothing really simple.
There are some hack. You can remap object to new scope using new controller
<div ng-controller="Ctrl as c" ng-init="c.makeAlias(obj.user.contact.home)">
<p>{{c.home}}</p>
</div>
where Ctrl just store makeAlias argument to it's scope as home
this.makeAlias(home) {
this.home = home;
}
Another solution is use $scope.$watch and remap object to the more convenient one.
I don't think Angular provides out of box supports for such things. But You can definitely create Domain object.Convert your http response into domain object. Within your domain object you can have helper method to get the required information.
For example in your case you can have Domain object like this
factory('User', function () {
return function (id,name, contact, purchase_hist) {
this.id = id;
this.name = name;
this.contact = contact;
this.purchase_hist = purchase_hist;
this.getContact = function(){
return this.contact.home;
};
};
})
you can use this domain object in your controller
$scope.user = // domain object return from your service
in html you can use
{{user.getContact().street}}
You can redefine your domain to make thing easier.It might be additional work. But when you have to do same thing again and again.One can enjoy writing code this way
I'd like to have a one-way (not one time) binding between an attribute on a directive, but i'm struggling with how to express this without attrs.$observe. The best I can come up with at the moment is to bind via &attr and invoke the variables I am binding to in my template e.g. {{attr()}}
app.controller('MainCtrl', function($scope) {
$scope.names = ['Original'];
setTimeout(function () {
$scope.names.push('Asynchronously updated name');
$scope.$apply();
}, 1000);
});
app.directive('helloComponent', function () {
return {
scope: {
'names': '&names'
},
template: '<li ng-repeat="name in names()">Hello {{name}}</li>'
}
});
<body ng-controller="MainCtrl">
<ul>
<hello-component names="names"/>
</ul>
</body>
Plunker
Is there a better way to do this that preserves the one-way binding without the need to invoke the bound properties?
Edit
I've updated the example code to clarify that I want to bind to an object, not just a string. So #attr (which works with a string attribute) is not a solution.
The "&" is actually the right thing to do. I have argued against this approach (with #JoeEnzminger, here and here) on the basis that it is semantically questionable. But overall Joe was right - this is the way to create a one-way binding to an actual object vs. "#" which binds to a string.
If you don't fancy an isolate scope, then you could get the same effect by using $parse:
var parsedName = $parse(attrs.name);
$scope.nameFn = function(){
return parsedName($scope);
}
and use it in the template as:
"<p>Hello {{nameFn()}}</p>"
I didn't see any mention of it in the other answers, but as of Angular 1.5, one-way bindings for objects are supported (see scope section in $compile docs for Angular 1.5.9):
< or <attr - set up a one-way (one-directional) binding between a local scope property and an expression passed via the attribute attr. The expression is evaluated 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. You can also make the binding optional by adding ?: <? or <?attr.
For example, given <my-component my-attr="parentModel"> and directive definition of scope: { localModel:'<myAttr' }, then the isolated scope property localModel will reflect the value of parentModel on the parent scope. Any changes to parentModel will be reflected in localModel, but changes in localModel will not reflect in parentModel. There are however two caveats:
one-way binding does not copy the value from the parent to the isolate scope, it simply sets the same value. That means if your bound value is an object, changes to its properties in the isolated scope will be reflected in the parent scope (because both reference the same object).
one-way binding watches changes to the identity of the parent value. That means the $watch on the parent value only fires if the reference to the value has changed. In most cases, this should not be of concern, but can be important to know if you one-way bind to an object, and then replace that object in the isolated scope. If you now change a property of the object in your parent scope, the change will not be propagated to the isolated scope, because the identity of the object on the parent scope has not changed. Instead you must assign a new object.
One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings back to the parent. However, it does not make this completely impossible.
In the example below, one-way binding is used to propagate changes in an object in the scope of a controller to a directive.
Update
As pointed out by #Suamere, you can indeed change properties of a bound object with one-way-binding; however, if the whole object is changed from the local model, then the binding with the parent model will break, as the parent and local scope will be referring to different objects. Two-way binding takes care of this. The code snippet was updated to highlight the differences.
angular.module('App', [])
.directive('counter', function() {
return {
templateUrl: 'counter.html',
restrict: 'E',
scope: {
obj1: '<objOneWayBinding',
obj2: '=objTwoWayBinding'
},
link: function(scope) {
scope.increment1 = function() {
scope.obj1.counter++;
};
scope.increment2 = function() {
scope.obj2.counter++;
};
scope.reset1 = function() {
scope.obj1 = {
counter: 0,
id: Math.floor(Math.random()*10000),
descr: "One-way binding",
creator: "Directive"
};
};
scope.reset2 = function() {
scope.obj2 = {
counter: 0,
id: Math.floor(Math.random()*10000),
descr: "Two-way binding",
creator: "Directive"
};
};
}
};
})
.controller('MyCtrl', ['$scope', function($scope) {
$scope.increment = function() {
$scope.obj1FromController.counter++;
$scope.obj2FromController.counter++;
};
$scope.reset = function() {
$scope.obj1FromController = {
counter: 0,
id: Math.floor(Math.random()*10000),
descr: "One-way binding",
creator: "Parent"
};
$scope.obj2FromController = {
counter: 0,
id: Math.floor(Math.random()*10000),
descr: "Two-way binding",
creator: "Parent"
};
};
$scope.reset();
}])
;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.js"></script>
<div ng-app="App">
<script type="text/ng-template" id="counter.html">
<h3>In Directive</h3>
<pre>{{obj1 | json:0}}</pre>
<button ng-click="increment1()">
Increment obj1 from directive
</button>
<button ng-click="reset1()">
Replace obj1 from directive (breaks binding)
</button>
<pre>{{obj2 | json:0}}</pre>
<button ng-click="increment2()">
Increment obj2 from directive
</button>
<button ng-click="reset2()">
Replace obj2 from directive (maintains binding)
</button>
</script>
<div ng-controller="MyCtrl">
<counter obj-one-way-binding="obj1FromController"
obj-two-way-binding="obj2FromController">
</counter>
<h3>In Parent</h3>
<pre>{{obj1FromController | json:0}}</pre>
<pre>{{obj2FromController | json:0}}</pre>
<button ng-click="increment()">
Increment from parent
</button>
<button ng-click="reset()">
Replace from parent (maintains binding)
</button>
</div>
</div>
Doing an attribute literally passes a string. So instead of doing this:
<hello-component name="name"/>
You can do this:
<hello-component name="{{name}}"/>
This may be essentially the same approach proposed by New Dev, but I solved a similar problem for myself by taking an object off of my isolate scope and creating a getter function for it which called scope.$parent.$eval(attrs.myObj).
In a simplified version that looks more like yours I changed:
app.directive('myDir', [function() {
return {
scope : {
id : '#',
otherScopeVar : '=',
names : '='
},
template : '<li ng-repeat="name in names">{{name}}</li>'
}
}]);
to
app.directive('myDir', [function() {
return {
scope : {
id : '#',
otherScopeVar : '='
},
template : '<li ng-repeat="name in getNames()">{{name}}</li>',
link : function(scope, elem, attrs) {
scope.getNames() {
return scope.$parent.$eval(attrs.myList);
};
}
}
}]);
That way whenever a digest runs your object is pulled as is from the parent scope. For me, the advantage to doing it this way was that I was able to change the directive from two-way to one-way binding (which took my performance from unusable to working fine) without changing the views that used the directive.
EDIT
On second thought I am not sure this is exactly one-way binding, because while updating the variable and running a digest will always use the updated object, there is no inherent way to run other logic when it changes, as one could with a $watch.
I have this code in angular:
<span ng-mouseover="item.show_description=true" ng-mouseleave="item.show_description=false" pointer="{x: item.x, y: item.y}">
{{item.label}}
</span>
<div class="description-popup" ng-show="!!item.description && item.show_description"
style="left: {{item.x}}px; top: {{item.y}}px">
<h2>{{item.label}}</h2>
<p>{{item.description}}</p>
<p>{{!!item.description && item.show_description}}</p>
</div>
It show popup correctly but if descritpion is null or empty string the popup still shows up. The last expression in that case show false. What I'm doing wrong here? Or maybe is there a bug there. I'm using Angular 1.0.6 (can't upgrade right now).
UPDATE:
I've create JSFiddle and it's seems that ng-show work as expected but not when I use pointer directive, that use mousemove event. The code for the directive is like this:
app.directive('pointer', function($parse) {
function objectParser(expr) {
var strip = expr.replace(/\s*\{\s*|\s*\}\s*/g, '');
var pairs = strip.split(/\s*,\s*/);
if (pairs.length) {
var getters = {};
var tmp;
for (var i=pairs.length; i--;) {
tmp = pairs[i].split(/\s*:\s*/);
if (tmp.length != 2) {
throw new Error(expr + " is Invalid Object");
}
getters[tmp[0]] = $parse(tmp[1]);
}
return {
assign: function(context, object) {
for (var key in object) {
if (object.hasOwnProperty(key)) {
if (getters[key]) {
getters[key].assign(context, object[key]);
}
}
}
}
}
}
}
return {
restrict: 'A',
link: function(scope, element, attrs) {
var expr = objectParser(attrs.pointer);
element.mousemove(function(e) {
var offest = element.offset();
scope.$apply(function() {
expr.assign(scope, {
x: e.pageX - offest.left,
y: e.pageY - offest.top
});
});
});
}
};
});
Isolate scope
The problem is that ng-show uses the scope of the element it's on. If it's on an element that has a directive with isolate scope, then it will use that isolate scope, and not have access to the outside scope.
In this particular case, I suspect that description-popup has isolate scope, which means ng-show only has access to the scope of that directive, which probably doesn't have the item that you're trying to test against.
What is isolate scope?
Isolate scope means the directive has its own scope that does not inherit from the surrounding scope. Normal scopes inherit from the surrounding scopes, so they have access to the data on that surrounding scope. Isolate scope does not.
Why would anyone ever want to use isolate scope?!
Reuse. If a directive is intended to be reused in lots of places, in lots of different scopes with a variety of properties on them, you don't want properties on the directive's scope to collide with the properties of the surrounding scope. And since the directive is supposed to be totally independent and not use the surrounding scope at all, it's often a good idea to give it an isolate scope. There are very few disadvantages to this. Really, the only thing that's likely to go wrong is when someone wants to put an ng-show or ng-hide on that element.
Solution
Put your ng-show on an extra <div> around the description-popup:
<div ng-show="!!item.description && item.show_description">
<div class="description-popup"
style="left: {{item.x}}px; top: {{item.y}}px">
<h2>{{item.label}}</h2>
<p>{{item.description}}</p>
<p>{{!!item.description && item.show_description}}</p>
</div>
</div>
This assuming that the behaviour in this JSFiddle is what you want.
This is probably only true for versions of Angular < 1.2. In 1.2, the behaviour of isolate scopes seems to have been cleaned up: https://docs.angularjs.org/guide/migration#isolate-scope-only-exposed-to-directives-with-scope-property
If I have a function that returns an object. eg.
$scope.getPoint = function()
{
//some calculation logic goes here
return {x:1,y:2};
}
And I want to display properties from it in a template:
<b>som html</b> x: {{getPoint().x}} y:{{getPoint().y}}
That would result in two calls to the function.
(I know, angular may call it a gazillion times anyway)
Is there any way to reuse the same return value in a template?
Something like:
<b>som html</b> <span ng-repeat="(key, value) in getPoint()">{{key}}: {{value}}</span>
should work, at least according to the docs.
EDIT: In fact it does: Plunk
As long as the value isn't going to change, why not make it a self-calling function?
$scope.myVar = (function () {
return { x: 1, y: 2 };
}());