Angular.js - binding a directive to a variable in the controller - angularjs

I want to bind a directive to a variable within a controller, but cannot work out how to do it from the Angular.js docs (nor searching the web, looking at the egghead videos).
I have the following html:
<body ng-app="MyApp">
<div ng-controller="triCtrl">
<div jqslider pleaseBindTo="firstValue"></div>
<br>
<br>
<br>
<br>
<div jqslider pleaseBindTo="secondValue"></div>
<p>{{firstValue.v}}</p>
<p>{{secondValue.v}}</p>
</div>
</body>
And the following JS:
function triCtrl($scope) {
$scope.firstValue = {"min":0, "max":100, "v":50};
$scope.secondValue = {"min":0, "max":1000, "v":450};
}
var myAppModule = angular.module('MyApp', []);
myAppModule.directive('jqslider', function() {
return {
link:function(scope, element, attrs) {
element.slider({
range: false,
min: scope.min,
max: scope.max,
value: scope.v,
slide: function( event, ui ) {
scope.v = ui.value;
scope.$apply();
}
});
}
};
});
I have tried several ways using scope:{ } with &, #, = etc, but I can't get it to work. Any ideas? I understand the pleaseBindTo attribute has to be captured somewhere, but I don't know where or how.

Some Observations:
1. Your directive is an attribute
2. You are passing values to attributes themselves.
Maybe you should change your directive to an element instead. Rewrite the code as follows:
<jqslider pleaseBindTo="firstValue"></jqslider>
Remove the div's and use the directive directly as an element. Next, in the definition of your directive, write the following:
myAppModule.directive('jqslider', function() {
return {
scope: {
pleaseBindTo: "=pleaseBindTo"
}
link:function(scope, element, attrs) {
element.slider({
range: false,
min: scope.pleaseBindTo.min,
max: scope.pleaseBindTo.max,
value: scope.pleaseBindTo.v,
slide: function( event, ui ) {
scope.pleaseBindTo.v = ui.value;
scope.$apply();
}
});
}
};
});
If you wish to still keep the directive as an attribute, you could try to access the value of pleaseBindTo inside the link function with the statement attrs.pleaseBindTo - I am not sure of this, need to check it out.

Related

Angularjs- Disable button until image is rendered [duplicate]

I've been searching for an answer to simple but not trivial question: What is a right way to catch image' onload event in Angular only with jqLite? I found this question , but I want some solution with directives.
So as I said, this is not accepted for me:
.controller("MyCtrl", function($scope){
// ...
img.onload = function () {
// ...
}
because it is in controller, not in directive.
Here's a re-usable directive in the style of angular's inbuilt event handling directives:
angular.module('sbLoad', [])
.directive('sbLoad', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
var fn = $parse(attrs.sbLoad);
elem.on('load', function (event) {
scope.$apply(function() {
fn(scope, { $event: event });
});
});
}
};
}]);
When the img load event is fired the expression in the sb-load attribute is evaluated in the current scope along with the load event, passed in as $event. Here's how to use it:
HTML
<div ng-controller="MyCtrl">
<img sb-load="onImgLoad($event)">
</div>
JS
.controller("MyCtrl", function($scope){
// ...
$scope.onImgLoad = function (event) {
// ...
}
Note: "sb" is just the prefix I use for my custom directives.
Ok, jqLite' bind method doing well its job. It goes like this:
We are adding directive' name as attribute in our img tag . In my case , after loading and depending on its dimensions , image have to change its class name from "horizontal" to "vertical" , so directive's name will be "orientable" :
<img ng-src="image_path.jpg" class="horizontal" orientable />
And then we are creating simple directive like this:
var app = angular.module('myApp',[]);
app.directive('orientable', function () {
return {
link: function(scope, element, attrs) {
element.bind("load" , function(e){
// success, "onload" catched
// now we can do specific stuff:
if(this.naturalHeight > this.naturalWidth){
this.className = "vertical";
}
});
}
}
});
Example (explicit graphics!): http://jsfiddle.net/5nZYZ/63/
AngularJS V1.7.3 Added the ng-on-xxx directive:
<div ng-controller="MyCtrl">
<img ng-on-load="onImgLoad($event)">
</div>
AngularJS provides specific directives for many events, such as ngClick, so in most cases it is not necessary to use ngOn. However, AngularJS does not support all events and new events might be introduced in later DOM standards.
For more information, see AngularJS ng-on Directive API Reference.

Angular - Bind directive value to controller object

I'm trying to pass an array from a controller to a directive and for some (probably obvious to you lot!) reason when the array values are updated in the controller it does not reflect in the directive. The controller obtains data from a service into an array and I want to pass that array to the directive to create a bar graph. I've put the key parts of the code below.
Here is my top level HTML
<div dash-progress
graph-data="{{dashCtrl.myProgress}}">
</div>
<div>
Other Stuff
</div>
My template HTML for the directive:
<div class="boxcontent" ng-show="dashCtrl.showProgress">
<div class="chart-holder-lg">
<canvas tc-chartjs-bar
chart-data="progress"
chart-options="options"
height="200"
auto-legend>
</canvas>
</div>
</div>
Controller:
angular
.module('myApp')
.controller('dashCtrl',['mySvc',
function(mySvc) {
var self = this;
this.myProgress = [];
this.getProgress = function() {
//logic must be in the service !
mySvc.getProgress().then(function(success) {
self.myProgress = mySvc.progress;
});
};
}]);
and the directive:
angular
.module('myApp')
.directive('dashProgress', [function() {
return {
restrict: 'AE',
templateUrl: 'components/dashboard/progress.html',
scope: {
graphData: '#'
},
link: function(scope,el,attrs) {
scope.progress = {
labels: ['Duration','Percent'],
datasets: [
{
label: 'Duration',
data: [scope.graphData.duration]
},
{
label: 'Percent',
data: [scope.graphData.percent]
}
]
};
scope.options = { };
}
}
}]);
If I set an initial values of the myProgress object in the controller then these do get reflected in the directive, but I don't get the real values that I need when they are returned to the controller from the service.
In your directive's scope, instead of this:
scope: {
graphData: '#'
}
try using this:
scope: {
graphData: '='
}
Don't use {{ }} when passing array to the directive with =. It will render the array in the view instead of passing a reference to directive's scope.
As far as I know, # is not only one-way binding, but also one-time binding and should be used mostly for string values (e.g. setting an html attribute while initializing directive). If you'd like to use #, you should firstly convert data to JSON, then pass it to directive with {{ }}, then parse it again in directive and after any change - manually recompile the directive. But it would be a little overkill, wouldn't it?
Conclusion
Just remove the curly brackets from the view and use = to bind value to directive's scope.
View
<div dash-progress
graph-data="dashCtrl.myProgress">
</div>
Directive
scope: {
graphData: '='
},
Update
Try one more thing. In dashCtrl, wrap myProgress with an object (you can change names to be more self-explaining - this is just an example):
this.graphData = {
myProgress: []
}
this.getProgress = function() {
mySvc.getProgress().then(function(success) {
self.graphData.myProgress = mySvc.progress;
});
}
Then, pass graphData to directive:
<div dash-progress
graph-data="dashCtrl.graphData">
</div>
Finally, substitute every scope.graphData with scope.graphData.myProgress. This way you make sure that scope.graphData.myProgress always refers to the same data because it's a property of an object.
If this still doesn't work, you will probably have to use a watcher and update properties of scope.progress manually.

Angular Directive attrs.$observe

I found this Angular Directive online to add a twitter share button. It all seems staright forward but I can't work out what the attrs.$observe is actually doing.
I have looked in the docs but can't see $observe referenced anywhere.
The directive just seems to add the href which would come from the controller so can anyone explain what the rest of the code is doing?
module.directive('shareTwitter', ['$window', function($window) {
return {
restrict: 'A',
link: function($scope, element, attrs) {
$scope.share = function() {
var href = 'https://twitter.com/share';
$scope.url = attrs.shareUrl || $window.location.href;
$scope.text = attrs.shareText || false;
href += '?url=' + encodeURIComponent($scope.url);
if($scope.text) {
href += '&text=' + encodeURIComponent($scope.text);
}
element.attr('href', href);
}
$scope.share();
attrs.$observe('shareUrl', function() {
$scope.share();
});
attrs.$observe('shareText', function() {
$scope.share();
});
}
}
}]);
Twitter
In short:
Everytime 'shareTwitterUrl' or 'shareTwitterText' changes, it will call the share function.
From another stackoverflow answer: (https://stackoverflow.com/a/14907826/2874153)
$observe() is a method on the Attributes object, and as such, it can
only be used to observe/watch the value change of a DOM attribute. It
is only used/called inside directives. Use $observe when you need to
observe/watch a DOM attribute that contains interpolation (i.e.,
{{}}'s). E.g., attr1="Name: {{name}}", then in a directive:
attrs.$observe('attr1', ...). (If you try scope.$watch(attrs.attr1,
...) it won't work because of the {{}}s -- you'll get undefined.) Use
$watch for everything else.
From Angular docs: (http://docs.angularjs.org/api/ng/type/$compile.directive.Attributes)
$compile.directive.Attributes#$observe(key, fn);
Observes an interpolated attribute.
The observer function will be invoked once during the next $digest fol
lowing compilation. The observer is then invoked whenever the interpolated value changes.
<input type="text" ng-model="value" >
<p sr = "_{{value}}_">sr </p>
.directive('sr',function(){
return {
link: function(element, $scope, attrs){
attrs.$observe('sr', function() {
console.log('change observe')
});
}
};
})

Changing src of only hovered ng-include element, where value of src is a $scope variable

I have multiple ng-include elements that have src attribute set to $scope.template_url.
I want to change src of hovered element only to new template but changing it's value will change all of elements. How can i implement it?
Html code:
<section class="parent">
<div data-ng-include data-src="template_url"></div>
</section>
Javascript (in controller):
angular.element(document).on('mouseover', '.parent', function(){
$scope.$apply(function () {
$scope.template_url = "path/to/new/template.html";
});
});
Writing jQuery dom manipulation is dirty and also don't works:
$(this).attr('data-src', "path/to/new/template.html");
I'd suggest making this a directive. Directives have their own scope, so you can still do the "on hover use a different template" idea, but for each individual one that is hovered.
<div>
<div data-some-directive=""></div>
</div>
var myApp = angular.module('myApp',[]);
myApp.directive('someDirective', function() {
return {
controller: function ($scope) {
$scope.model = "Hello"
$scope.mouseover = function () {
$scope.model = "Hovered!";
};
},
scope:{},
restrict: 'AE',
replace: true,
template: '<div><input ng-mouseover="mouseover()" ng-model="model"></div>',
};
});
Heres a fiddle to see it in action.
Tweak the template variable in the directive to use a variable on your model for the include url.
By the way, angular already has a mouseover handler, so i've just linked that into the controller with ng-mouseover in the template.

How to validate dynamic form fields in angular directive?

I would like to create form with fields created in directive. Data binding of data working correctly but validation doesn't work.
this is html:
<body ng-controller="MainCtrl">
<h1>form</h1>
<form name="form">
<div ng-repeat="conf in config">
<div field data="data" conf="conf"></div>
</div>
</form>
<pre>{{data|json}}</pre>
</body>
controller and field directive:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {name: '', age: ''}
$scope.config = [
{field: 'name', required:true},
{field: 'age'}
];
});
app.directive('field', function ($compile) {
return {
scope: {
data: '=',
conf: '='
},
link: function linkFn(scope, element, attrs) {
// field container
var row = angular.element('<div></div>');
// label
row.append(scope.conf.field + ': ');
// field input
var field = angular.element('<input type="text" />');
field.attr('name', scope.conf.field);
field.attr('ng-model', 'data.' + scope.conf.field);
if (scope.conf.required) {
field.attr('required', 'required');
}
row.append(field);
// validation
if (scope.conf.required) {
var required = angular.element('<span>required</span>');
required.attr('ng-show',
'form.' + scope.conf.field + '.$error.required');
row.append(required);
}
$compile(row)(scope);
element.append(row);
}
}
});
problem is that validation for field name doesn't work and validation text required is never shown. May be form in ng-show is unknown in directive. But I don't know how to pass form into field directive. Can you help me how to fix it? Thanks.
here is live code: http://plnkr.co/edit/j0xc7iV1Sqid2VK6rMDF?p=preview
Todo:
before:
$compile(row)(scope);
element.append(row);
after:
element.append(row);
$compile(row)(scope);
p/s in 'planker' for facilities add css:
.ng-invalid {
border: 1px solid red;
}
You'll need to use ng-form directive and push the dynamic field directly into form object.
This thread can help you out:
https://github.com/angular/angular.js/issues/1404
Here is a plunker forked from yours to fix you're issue:
http://plnkr.co/edit/qoMOPRoSnyIdMiZnbnDF?p=preview
To summarize, I added a watch that will toggle the error message instead of using the ng-show directive. Things can get hairy when you attempt to dynamically add a directive within a directive link. For a simple use case as this, it is quicker to add your own watch.
You may also look at this directive which is preconfigured to handle many use cases for validation as well as allow you to create custom validations easily https://github.com/nelsonomuto/angular-ui-form-validation
var toggleRequiredErrorMessage = function (invalid) {
if(invalid === true) {
addRequiredErrorMessage();
} else {
removeRequiredErrorMessage();
}
};
scope.$watch( watchIfRequired, toggleRequiredErrorMessage );

Resources