How to bind the ng-repeat variable to a custom angular directive? - angularjs

In Angular (v1.3) how do you bind the variable from an ng-repeat to a custom directive?
All the examples seem to bind to a $scope variable instead. eg given a list of products and a custom directive called product, how can we do something to this effect:
<li ng-repeat="product in products">
<product item="product" />
</li>
Where the directive is created as:
(function () {
'use strict';
var productDirective = function () {
return {
restrict: 'E',
scope: {item: '='},
templateUrl: '/views/product.html',
};
};
angular
.module('myApp')
.directive('product', productDirective);
})();
And it has a very simple template:
<p>{{item.name}} {{item.price}}</p>
This fails because scope: '=' only binds to $scope, not to ng-repeat.
I was able to get it to work with
return {
restrict: 'E',
scope: {item: '#'},
templateUrl: '/views/product.html',
link: function(scope, element, attributes){
scope.item = scope.$eval(attributes.item);
}
};
But the usage of $eval is not really acceptable of course (bad style).
What is the correct angular way to achieve this?

This fails because scope: '=' only binds to $scope, not to ng-repeat.
I don't quite understand what you mean by the above, but the = in the bindings context is useful when you bind an object from the parent scope of the directive - which is in fact the controller where you have used the product directive - to the isolated scope of the directive, and NOT the ng-repeat.
Therefore, in your case, you don't actually need to bind an object from a parent controller, to the directive's isolated scope with the $eval trick.
Just make sure that in the particular parent controller you have defined the array of products properly.
Here's a Demo of how you can get it working.

This fails because scope: '=' only binds to $scope, not to ng-repeat.
It should NOT fail because ng-repeat creates a scope for every iteration and puts the current product to it along with the other loop vars like $index. So, the product is actually on the scope and you can bind to it normally.
I created a FIDDLE with no changes to your code to confirm this.

Try adding a controller to the productDirective:
var productDirective = function () {
return {
restrict: 'E',
scope: {item: '='},
templateUrl: '/views/product.html',
controller: ['$scope', function($scope) {}]
};
};

Related

How to pass object to directive through attribute in AngularJS?

I want to pass object (reference - two way binding) through ATTRIBUTE not by isolated scope. How can I do this? Because code bellow passing string instead of object:
HTML
<tr ng-form="rowForm" myDirective="{{row.data}}">
Directive
angular.module("app").directive("myDirective", function () {
return {
require: ["^form"],
restrict: "A",
link: function (scope, element, attrs, ctrls) {
scope.$watch(function () {
return attrs.myDirective;
}, function (newValue, oldValue) {
// .....
Directives can do two-way data binding without parsing or compile anything manually, sorry for not delivering the plunker but it's rebelius and won't save for me
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.myObj = {name: 'Tibro', age: 255}
})
.directive('myDirective', function(){
return {
scope: {
'myAttribute': '='
},
template: '{{myAttribute}}',
link: function(scope){
scope.myAttribute.age= 31
}
}
})
HTML
<body ng-controller="MainCtrl">
controller: {{myObj}} <br/>
directive: <my-directive my-attribute="myObj"></my-directive>
</body>
OUTPUT
controller: {"name":"Tibro","age":31}
directive: {"name":"Tibro","age":31}
you can see from the output that passed object has been binded two-way and change made in directive is reflected on controller level
The result of {{ }} interpolation is a string. An object can't be passed like that.
Bindings are idiomatic here and thus preferable. The whole thing becomes messy when the directive is forced to use parent scope. However, it can be done by parsing scope properties manually with $parse:
$scope.$watch(function () {
var myDirectiveGetter = $parse($attrs.myDirective);
return myDirectiveGetter($scope);
}, ...);
This is a job for binding (< or =, depending on the case). If isolated scope isn't desirable, this can be done with inherited scope and bindToController:
scope: true,
bindToController: {
myDirective: '<'
},
controllerAs: `vm`,
controller: function ($scope) {
$scope.$watch('vm.myDirective', ...);
}
Notice that directive attribute is my-directive, not myDirective.
Couple of things:
myDirective in the tr should be my-directive, as per Angular's
conventions.
{{row.data}} prints the variable, you need to pass it without the
{{}} for it to go through as an object.

mixing isolated scope and controllerAs syntax

I have a directive that is need of a variable that resides on it's parent controller
<hack-chart-controls counttime="vm.countInMinutes"></hack-chart-controls>
directive:
function hackChartControls($log, $parse) {
var directive = {
restriction: 'AE',
scope: {
counttime: '='
},
templateUrl: '/app/components/hackChartControls.html',
link: link
};
return directive;
Based on this answer I was able to watch when that variable changes in the directive.
However, in the directive markup because I'm using the ControllerAs syntax I am using vm as my scope variable. For Example:
<div class="close"><i class="fa fa-close" ng-click="vm.close()"></i></div>
prior to making the scope: { counttime: '=' } change these ng-click functions worked just fine because it inherited scope from the parent without isolating the scope.
How can I get the click function to work again?
I would reference the action inside the isolate scope, or not use isolate scope at all. It depends why you want to use isolate scope in the first place. The purpose of isolate scope is to encapsulate the directive and make it independent of the outside scope.
function hackChartControls($log, $parse) {
var directive = {
restriction: 'AE',
scope: {
counttime: '=',
action: '&',
},
templateUrl: '/app/components/hackChartControls.html',
link: link
};
return directive;
}
And your template:
<div class="close"><i class="fa fa-close" ng-click="action()"></i></div>
Use it like this:
<hack-chart-controls counttime="vm.countInMinutes" action="vm.close()"></hack-chart-controls>

How to change the value of a $scope.variable in a controller from a directive in angularJS?

I am very new to the concepts of angularJS. The problem I am facing is I have declared a variable $scope.myVariable = true in my controller. I need to toggle the the value of $scope.myVariable from the directive. Is it possible to do that.. if yes how??
Please help..
From a directive, you can access the scope through the link function:
app.directive('myDirective', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: 'MyController',
templateUrl: 'my-template.html',
link: function (scope, element) {
scope.name = 'Jeff';
}
};
});
See https://docs.angularjs.org/guide/directive
If you want to use Angularjs Directive, you can use 3 methods of it's scope
1) true (inherit)
2) false (not inherit)
3) {} (Isolated)
if you use 1) method, you have to work with it's parent scope, because that variable is in parent scope
if you use 2) method you don't have to do anything else.You are working with current scope
if you want more with examples here is a cool link
http://www.w3docs.com/snippets/angularjs/bind-variable-inside-angularjs-directive-isolated-scope.html
You can pass the reference of "myVariable" from your main controller to the directive like this :
//index.html
<my-directive myvar="myVariable"></my-directive>
//myDirective.js
app.directive('myDirective', [function () {
return {
restrict: "E",
require: 'myvar',
scope: {
myvar:"=" //"=" mean, you can set the variable (your toggle function needs that)
},
templateUrl: "./directives/myDirective/myDirectiveView.html",
controller: "myDirectiveController as myDirectiveCtrl"
}
}]);
With this solution, myvar in the directive contains the reference to myVariable of the main controller and you can use it with $scope.myvar in the directive.
EDIT :
for example, if you have a list for object contains src and compression var like this :
{ src:"path/to/img", compressed:false}
<div ng-repeat="imgObj in imgObjList">
<my-directive myvar="imgObj"></my-directive>
</div>
In the my-directive view :
<img ng-src="myvar.src" ng-show="myvar.compressed == true"/>
In your my-directive Controller :
you can set myvar.compressed to true when encoding is finished

In angular.js, can a directive controller access data in a page controller that loaded it?

In angular.js, can a directive controller access data in a page controller that loaded it?
/**
* Profile directive
*/
.directive('profile', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/partials/users/_profile.html',
scope: {
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
$scope.show = angular.isDefined($scope.show) ? $scope.show : { follow: true, link: true };
$scope.currentUser = $rootScope.currentUser;
//do stuff here and then set data in UserShowCtrl
}
};
});
The <profile user="user"></profile> method is called from ./users/show.html which uses the UserShowCtrl controller.
Is there anyway I can use scope on the profile directive with its own controller and still be able to pass data to the UserShowCtrl?
Even though the profile can be isolated to its own functionality, it still needs to set some data on the page level in the UserShowCtrl controller.
Here is where _user.html is loading the <profile> directive. The data for the page is served by the UserShowCtrl and has some collections that get updated when things happen, like following a user.
<ol class="following" ng-show="showConnections == 'following'">
<li ng-repeat="following in user.following">
<profile user="connections[following]"></profile>
</li>
</ol>
Right now there is an ng-click="follow(user)"> that is happening in the _profile.html. I would like to be able to have the directive handle this but also update the collections in the UserShowCtrl.
Edit: here is a plunker demonstrating what I'm trying to do:
http://plnkr.co/edit/9a5dxMVg9cKLptxnNfX3
You need to use a service in order to share any information between controllers, directives, services
something like
angular.module('myapp',[]).
service('myservice',function(){
return {a:'A',b:'B'}
}).
controller('mycontroller',['myservice',function(myservice){
//do someting with myservice
}]).
directive('mydirective',['myservice',function(myservice){
//do someting with myservice
}]);
there controller and directive access the same data through the service
You can access the parent scope from your directive with $scope.$parent.myvar.
myvar will be resolved in parent scope, which means prototypical scope inheritance is used to resolve the variable.
However, this does not guarantee that myvar is coming from the same scope as UserShowCtrl since its possible that any scope in between the 'profile' directive and UserShowCtrl's scope may override 'myvar'.
A better solution would be to use directive-to-directive communication. There are generally two ways for directives to communicate:
Through attributes passed into your directive. You've already used this method to import 'user' and 'show' from parent scope into your directive's isolated scope.
Requiring another directive. When you use 'require: ^UserShow', you are specifying that your 'profile' directive requires another directive as a dependency. The '^' means that it will search for the directive on the current element, or any parent element further up the DOM tree. UserShow's controller is then passed to your link function:
.directive('UserShow', function () {
return {
restrict: 'E',
controller: function($scope){
$scope.myvar = 'test';
this.setMyVar = function(var) {
$scope.myvar = var;
}
}
};
});
.directive('profile', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/partials/users/_profile.html',
require: '^UserShow',
scope: {
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
},
link: function(scope, element, attr, UserShowCtrl) {
UserShowCtrl.setMyVar('hello world!);
}
};
});
HTML:
<user-show>
<profile>...</profile>
</user-show>
I am not quite sure what your after.
You are already having 2 two-way data bindings, which means that if you change user in your directive, that will also flow to the outside scope.
So you already have a solution in front of you...
So if that is not "good enough", there is something missing in your question.
Here is an illustration: http://plnkr.co/edit/qEH2Pr1Pv7MTdXjHd4bD?p=preview
However, if you use something in your outside template that creates a child scope, binding it as "value" there is NOT enough, you need to have a . in there.
But that is where there is missing something to the question, if you share your show.html I may be able to find where the scope breaks apart and explain why...
Relevant Source from demo.js:
app.directive('profile', function () {
return {
restrict: 'E',
replace: true,
template: '<div><input type="text" ng-model="user"></input></div>',
scope: { //defines an isolate scope.
user: '=',
show: '=?'
},
controller: function($scope, $rootScope){
$scope.show = angular.isDefined($scope.show) ? $scope.show : { follow: true, link: true };
$scope.currentUser = $rootScope.currentUser;
$scope.user = "Changed by scope!";
//do stuff here and then set data in UserShowCtrl
}
};
});
app.controller('UserShowCtrl', function($scope) {
$scope.value = "Value set outside!";
$scope.alertValue = function() {
alert($scope.value);
}
});
Relevant Source from home.html:
<div ng-controller="UserShowCtrl">
{{ value }}
<profile user="value"></profile>
<button ng-click="alertValue()">ALERT!</button>
</div>

Angularjs: Can transcluded elements "live" in the parent controller's scope?

I would like directives in the transcluded content to "live" in the directive's parent controller
ex: in the view below, I would like the binding "varInMyController" to
be bound to the variable of the same name in MyController.
I could explicitely pass variables in the directive scope:{aVar: '='},
but I want to avoid this, because the directive needs to be generic.
is this possible ?
<div ng-controller="MyController">
<div my-directive>
{{varInMyControllerScope}}
</div>
</div>
angular.module('myModule')
.directive('myDirective', [function() {
return {
transclude: true,
replace: true,
templateUrl: '<div><div ng-transclude></div></div>',
scope: {
options: '='
},
link: function(scope, element, attrs) {}
}
}])
angular.module('MyModule')
.controller('myController', ['$scope'function($scope) {
$scope.varInMyControllerScope = "hello"
}])
See Associated Plunker.
The most crucial problem of your above is this line in the directive
templateUrl: '<div><div ng-transclude></div></div>'
use template instead since it isn't a URL.
If this isn't a partial code, then another problem might be the initialization of your module. It should look like this from the start:
angular.module('myModule', [])
Another problem is the spelling typo of your second invocation for the angular.module() method:
angular.module('MyModule');
It should be
angular.module('myModule');
Lastly is the comma between the array of passed in the controller function is not separated by ,
['$scope'function($scope) {
$scope.varInMyControllerScope = "hello"
}]
It should be
['$scope', function($scope) {
$scope.varInMyControllerScope = "hello"
}]

Resources