mixing isolated scope and controllerAs syntax - angularjs

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>

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.

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

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

Directive scope not getting updated in controller

My directive's controller is not getting updated with the scope that was set using the '=' two-way-binding.
Here is my directive:
.directive('navigation', function() {
return {
restrict: 'E',
scope: {
selection: '=?selectedItem',
goforward: '&onForward'
},
controller: function() {
var vm = this;
vm.hideForward = !vm.selection
},
controllerAs: 'vm',
bindToController: true,
template: '<button ng-hide="vm.hideForward" ng-click="vm.goforward()">Continue</button>'
};
});
Here is my html file where I use the directive:
<div class='product' ng-click='ctrl.selectedItem = true'>item</div>
<navigation on-forward="ctrl.goForward()" selected-item='ctrl.selectedItem'></navigation>
note: the ctrl.goForward() works just fine.
The vm.selectedItem in the html's controller is only set to true once the product div is clicked.
I expected the ctrl.selectedItem to get passed into my directive's controller and modify the vm.hideForward value, except this is not happening.
I want to be able to change whether the navigation directive is visible and/or active depending on variables that are passed into it from whatever controller's scope I used my directive in.
If I place a <div>{{vm.selectedItem}}</div> inside my directive's template, that does print out properly depending on how ctrl.selectedItem that value changes. My issue is getting the directive's controller to change as well.
How am I setting up this scope binding improperly? I am using angular 1.5.3
You dont need the double brackets for binding a function to ng-click, use ng-click="vm.goforward()"
Pass the function to the directive as on-forward="ctrl.goForward", if you use parenthesis you will be passing the result of the function call instead.
Also for, ng-click='ctrl.selectedItem === true' you should use ng-click='ctrl.selectedItem = true' to set the value, as === is a comparison operator.
ctrl.selectedItem seems to be a variable from the present controller. So while passing it as attribute, you need to pass it as '{{ctrl.selectedItem}}" .
Try using:
**<navigation on-forward="ctrl.goForward()" selected-item='{{ctrl.selectedItem}}'></navigation>**
Try this
.directive('navigation', function() {
return {
restrict: 'E',
scope: {
selection: '=selectedItem',
goforward: '&onForward'
},
controller: function(scope) {
var vm = this;
vm.hideForward = !scope.selection
},
controllerAs: 'vm',
bindToController: true,
template: '<button ng-hide="vm.hideForward" ng-click="vm.goforward()">Continue</button>'
};
});

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

Can I two-way bind input field in directive template?

Having a following template in templateUrl:
<input name="foo" ng-model="test">
directive:
app
.directive('bar', function() {
return {
link: function link(scope, element, attrs, ctrl) {
scope.$watch(scope.test, function(newVal) {
console.log(val);
});
},
restrict: 'E',
templateUrl: 'templates/foo.html'
};
});
can I two-way bind it in directive so I scope.$watch input variable?
I tried using ng-bind and ng-model, but I cannot access that variable in scope of my directive.
Edit
Added directive code.
Change:
scope.$watch(scope.test, ...
to
scope.$watch('test', ...
and it should work. The first argument to $watch is the (so called) watchExpression. It will be evaluated against the relevant scope. When using a string you can basically use everything you would also use in the views/templates.
Mind that this will break again if you start using isolated scopes.

Resources