Why using $watch on a directive attribute is not working? - angularjs

I have directive that use another directive that is setting his property 'gridWidth', but I'm trying to watch that property changes and its not working at all. Can anyone explain what I'm doing wrong?
angular.module('myApp')
.directive('myGrid', function () {
'use strict';
return {
restrict: 'E',
replace: true,
scope: {
gridWidth: '='
},
controller: ['$scope', function(scope) {
scope.$watch('gridWidth', function (newValue, oldValue) {
console.log("width is changed");
}, true);
}],
template: '<div element-resize gridWidth="{{windowWidth}}"></div>'
};
});
The 'windowWidth' is changes fine and I can see it in HTML dom changes, but the $watch not working for some reason so I can't do anything when it is being updated. Here is the another directive updating the first one with width:
angular.module('myApp')
.directive('elementResize', function () {
'use strict';
return {
restrict: 'A',
controller: ['$scope', '$element', function($scope, $element) {
$scope.getElementDimensions = function () {
return { 'h': $element.height(), 'w': $element.width() };
};
$scope.$watch($scope.getElementDimensions, function (newValue, oldValue) {
$scope.windowHeight = newValue.h;
$scope.windowWidth = newValue.w;
}, true);
}]
};
});

Related

Get ng-model from a input inside directive and put it in an attribute of directive

I have "nibTextbox" directive with an input inside it,input has a ng-model,I want that ng-model value always available in "value" attribute of directive.(I don't wanna use replace)
angular.module('nib', [])
.directive('nibTextbox', function () {
return {
restrict: 'E',
scope: {
id: '#',
title: '#',
},
compile: function (element, attributes) {
var linkFunction = function (scope, element, attributes) {
}
return linkFunction;
},
controller: ['$scope', '$http', function ($scope, $http) {
}],
template: '<div value="{{nibTextBoxValue}}"><img src="" alt=""/><label>{{title}}</label><input id="{{id}}_txt" type="text" ng-model="nibTextBoxValue"></input></div>'
};
});
<nib-textbox id="ngArmin1" title="ngArmin1Title" value="{{nibTextBoxValue}}"></nib-textbox>
value is not valid for a <div> element. So let's change it to data-div.
This is how it will look more or less (I usually work with typescript but I'll use plain javascript to pass the idea):
angular.module('nib', [])
.directive('nibTextbox', function () {
return {
restrict: 'E',
scope: {
id: '#',
title: '#',
},
compile: function (element, attributes) {
var linkFunction = function (scope, element, attributes) {
}
return linkFunction;
},
// Injected $element for manipulating attributes
controller: ['$scope', '$http', '$element', function ($scope, $http, $element) {
$scope.$watch("nibTextBoxValue", function(newValue) {
$element.attr("data-value", newValue);
});
}],
templateUrl: 'template.html' // Extracting the template to a file
};
});
The directive template (template.html):
<div>
<img src="" alt=""/><label>{{title}}</label>
<input id="{{id}}_txt" type="text" ng-model="nibTextBoxValue"></input>
</div>
In addition, remove the value attribute from your directive:
<nib-textbox id="ngArmin1" title="ngArmin1Title"></nib-textbox>
the answer:use $watch
change controller definition to this:
controller: ['$scope', '$http', '$element', function ($scope, $http, $element) {
$scope.$watch("nibTextBoxValue", function (nv) {
$element.attr("value", nv);
});
}]

Calling controller function from directive using bindToController

I wrote a plunker to see how to use bindToDirective to isolate scopes and using directive controller to call main controller function, but, I am doing something wrong. Could you suggest?
This is the plunker: http://plnkr.co/edit/UJLjTmIiHydHr8qRzAsX?p=preview
Code sample:
.controller('Ctrl', function() {
var self = this;
self.func = function() {
console.log('In the controller function');
};
})
.directive('myDirective', [ function() {
var self = {};
self.link = function (scope, elem, attrs, ctrl) {
elem.bind('click', function () {
ctrl.ctrlFunc();
});
elem.addClass('fa fa-file-excel-o fa-lg');
};
return {
restrict: 'E',
scope: {},
controller: function () {
},
controllerAs: 'DirCtrl',
bindToController: {
ctrlFunc: '&'
},
link: self.link
};
}])
html sample to associate main controller function to directive:
<div ng-controller="Ctrl">
<my-directive ctrlfunc="Ctrl.func()"></my-directive>
</div>
You have a number of issues:
You need a hyphen in your directive argument name and you should be passing the function reference, not calling the function directly (with params):
<my-directive ctrl-func="ctrl.func"></my-directive>
Second, you are using alias syntax in your controller (var self = this;), but not in your template. You need to update it to the following:
<div ng-controller="Ctrl as ctrl">
<my-directive ctrl-func="ctrl.func"></my-directive>
</div>
Finally, pass down the function reference with two-way binding instead of with & since that passes down values for implicit evaluation.
bindToController: {
ctrlFunc: '='
},
See working plunkr
I'm not sure you need bindToController...
This version calls your Ctrl's function: http://plnkr.co/edit/Rxu5ZmmUAU8p63hR2Qge?p=preview
JS
angular.module('plunker', [])
.controller('Ctrl', function($scope) {
$scope.func = function() {
console.log('In the controller function');
};
}) angular.module('plunker', [])
.controller('Ctrl', function($scope) {
$scope.func = function() {
console.log('In the controller function');
};
})
.directive('myDirective', [ function() {
return {
template: '<pre>[clickme]</pre>',
replace: true,
restrict: 'E',
scope: {
target: '&'
},
link: function (scope, elem, attrs) {
elem.bind('click', function () {
var fn = scope.target && scope.target(scope);
fn && fn();
});
elem.addClass('fa fa-file-excel-o fa-lg');
}
};
}])
HTML
<div ng-controller="Ctrl">
<my-directive target="func"></my-directive>
</div>

$scope.$watch not detecting changes in directive in same module

I'm implementing a watch in the controller, when user toggles a button. I have a watch inside the directive and that is working, e.g. it detects that the scope variable has changes but if I have a watch inside the controller it does not detects
<button toggle-button active-text="Yes" inactive-text="No" ng-model="light.state">Show millions</button><br>
here is the code:
'use strict';
angular.module('fiveYearOverview', ['fiveYearOverviewServices', 'fiveYearOverviewDirectives'])
.controller('fiveYearCtrl', [
'$scope', 'reports',
function ($scope, reports) {
//not working
$scope.$watch('lightState', function (newValue, oldValue) {
if (newValue)
console.log("I see a data change!");
}, true);
}
])
.directive('toggleButton', function () {
return {
require: 'ngModel',
scope: {
activeText: '#activeText',
inactiveText: '#inactiveText',
lightState: '=ngModel'
},
replace: true,
transclude: true,
template: '<div>' +
'<span ng-transclude></span> ' +
'<button class="btn" ng-class="{\'btn-primary\': state.value}" ng-click="state.toggle()">{{activeText}}</button>' +
'<button class="btn" ng-class="{\'btn-primary\': !state.value}" ng-click="state.toggle()">{{inactiveText}}</button>' +
'</div>',
link: function postLink(scope) {
scope.lightState = scope.inactiveText;
scope.state = {
value: false,
toggle: function () {
this.value = !this.value;
scope.lightState = this.value ? scope.activeText : scope.inactiveText;
console.log(scope.lightState);
//working
scope.$watch('lightState', function (newValue, oldValue) {
if (newValue)
console.log("I see a data change!");
}, true);
}
};
}
}
});
What is that i'm doing wrong ?
By defining a scope in the directive you are creating an isolated scope for it. Thats why you cant access its members from the controller.
lightState is being declared in a scope below that of fiveYearCtrl making it inaccessible as outlined here.
An alternative solution could be to define the callback function in fiveYearCtrl and call it from the directive.
.controller('fiveYearCtrl', [
'$scope', 'reports',
function ($scope, reports) {
this.consoleLog = function (newValue, oldValue) {
if (angular.isDefined(newValue))
console.log("I see a data change!");
};
}
])
link: function postLink(scope, element, attrs, ctrl)
{
scope.lightState = scope.inactiveText;
scope.state = {
value: false,
toggle: function () {
this.value = !this.value;
scope.lightState = this.value ? scope.activeText : scope.inactiveText;
console.log(scope.lightState);
//working
scope.$watch('lightState', ctrl.consoleLog, true);
}
};
}

How sub directive is passing data from his isolated scope to the parent scope directive?

I have created a very nice sub-directive using element-resize directive that calculate the actual size of an element:
angular.module('myApp')
.directive('subNode', function () {
'use strict';
return {
restrict: 'E',
replace: true,
scope: {
displayName: '#',
nodeWidth: '#'
},
controller: ['$scope', '$element', function($scope, $element) {
}],
template: '<div element-resize>{{displayName}}-{{elementWidth}}</div>'
};
});
If I run this I can see the 'elementWidth' is updeting great, but I don't understand how my parent directive is now reading the child scope data being created? If someone can give a simple example how they should share data it will be a big help.
Here is my parent directive which should calculate all sub-directive created width: Don't understand how to that.
angular.module('myApp')
.directive('nodeCollection', function () {
'use strict';
return {
restrict: 'E',
replace: true,
scope: {
nodes: '=',
},
controller: ['$scope', '$element', '$attrs', '$rootScope', function(scope, $element, $attrs, $rootScope) {
}],
template: '<div element-resize><ul><li ng-repeat="node in nodes"><sub-node display-name="{{node.name}}"></sub-node></li></ul></div>'
};
}
);
The element-resize directive:
angular.module('myApp')
.directive('elementResize', function () {
'use strict';
return {
restrict: 'A',
controller: ['$scope', '$element', function($scope, $element) {
$scope.getElementDimensions = function () {
return { 'h': $element.height(), 'w': $element.width() };
};
$scope.$watch($scope.getElementDimensions, function (newValue, oldValue) {
$scope.elementHeight = newValue.h;
$scope.elementWidth = newValue.w;
}, true);
}]
};
}
);

Why do I get the same element sizes on different DOM elements using resize directive?

I'm using this simple directive to know the size (height/width) of an DOM element:
angular.module('myApp')
.directive('elementResize', function () {
'use strict';
return {
restrict: 'A',
controller: ['$scope', '$element', function($scope, $element ) {
$scope.getElementDimensions = function () {
return { 'h': $element.height(), 'w': $element.width() };
};
$scope.$watch($scope.getElementDimensions, function (newValue, oldValue) {
$scope.windowHeight = newValue.h;
$scope.windowWidth = newValue.w;
}, true);
}]
};
});
This is the HTML template:
<div element-resize>Element1.width:{{windowWidth}}</div><div element-resize>Element2.width:{{windowWidth}}a div with more width space</div>
I have two divs that are brothers, but using my directive I get the same sizes of the latest, instead of getting each one of them the real size. What did I missed here?
UPDATE:
If I had ng-controller to the first div it will work. Why?
Why not just use link instead of controller. Here's an example plunker.
app.directive('elementResize', function() {
'use strict';
return {
restrict: 'A',
template: 'Element.width:{{windowWidth}}',
scope: {},
link: function(scope, element, ctrl) {
console.log(element)
scope.getElementDimensions = function() {
return {
'h': element[0].clientHeight,
'w': element[0].clientWidth
};
};
scope.$watch(scope.getElementDimensions, function(newValue, oldValue) {
scope.windowHeight = newValue.h;
scope.windowWidth = newValue.w;
}, true);
}
};
});

Resources