angular scope.$watch not working - javascript

I have a directive where i bind the tabInfo to tabdata which is an array.
angular.module('widgets')
.directive('tabs', function() {
return {
restrict: 'E',
require: 'info',
transclude: true,
scope: {},
controllerAs: 'tabs',
bindToController: {
tabInfo: '=tabdata'
},
templateUrl: 'Template.html',
link: function(scope, element, attrs, tabs) {
scope.$watch('tabs.tabInfo', function() {
tabs.populateDataProvider();
}, true);
},
controller: ['$filter', '$state', function($filter, $state) {
}]
};
});
I need to watch on the "tabInfo" for any change and populate some data. However , my watch is not called at all. Is there something wrong here , in the way that I am refering to tabs.tabInfo?

A better solution to this would be to have an ng-if along with the directive so as to make sure that the directive is rendered only when there is data to populate . This avoids the need of having a watcher inside the directive .
So you could use ,
<tabs ng-if="$ctrl.tableData.length" tab-info="$ctrl.tableData"></tabs>
For your question above , you need to bind this to a variable to tabs
to get it work .
var tabs = this;
EDIT:
There is also a correction on your code . It should be ,
angular.module('widgets')
.directive('tabs', function() {
return {
restrict: 'E',
require: 'info',
transclude: true,
bindToController: true,
controllerAs: 'tabs',
scope: {
tabInfo: '=tabdata'
},
templateUrl: 'Template.html',
link: function(scope, element, attrs, tabs) {
scope.$watch('tabs.tabInfo', function() {
tabs.populateDataProvider();
}, true);
},
controller: ['$filter', '$state', function($filter, $state) {
}]
};
});

Related

AngularJS Custom directives - Issue accessing controllerAs variables/objects in directive link

I've just started refactoring my code to do DOM manipulation and functions in directives instead of inside controllers as I had previously been doing, but I'm having issues accessing variables/objects defined using controllerAs 'this' syntax within the controller from which I need them to be inherited.
I've tried using bindToController as below, where I've added the different objects that are used in the directive function, but when I try to access these withink the 'link', they're all returning as undefined in the console.
Example here. 'this.test' defined in controller, tried accessing this in the directive in a console log message.
Controller:
app.controller('notificationsController', function($scope, $state, $http, $document, $mdDialog, $filter, $timeout, $mdToast) {
this.test = 'TEST';
Directive:
app.directive('clearNotifications', function($mdDialog, $mdToast, $timeout) {
return {
controller: 'notificationsController',
controllerAs: 'notifications',
scope: {},
bindToController: {
notifications: '=',
filters: '=',
test: '#'
},
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('click', function() {
console.log('notifications.test string test: ' + notifications.test);
this in controller is different with controllerAs in directive , in directive you should use ctrl or model to binding.
var app = angular.module("app", []);
app.controller("notificationsController", function($scope) {
this.test = "foo!";
})
app.directive("clearNotifications", function() {
return {
controller: 'notificationsController',
controllerAs: 'notifications',
scope: {},
bindToController: {
notifications: '=',
filters: '=',
test: '#'
},
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
element.bind('click', function() {
console.log('notifications.test string test: ' + ctrl.test);
})
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<button clear-notifications>clearNotifications</button>
</div>

Angular: Directive: How to pass require form AND controller to link function

I have a directive called formNavHandler to handle dirty checking and navigation from page to page. formNavHandler relies on a controller called CoolFormCtrl and a form called coolForm. I want to pass both CoolFormCtrl and coolForm to the link function of formNavHandler
angular.module('cool').directive('formNavHandler', [
'$log', function($log) {
return {
restrict: 'A',
scope: {
disabled: '=coolFormDisabled'
},
controller: 'CoolFormCtrl',
require: 'form',
link: function(scope, elem, attrs, WhatsThis) {
$log.log(WhatsThis);
...
}
};
}
]);
used like so:
<form name="coolForm" form-nav-handler=true cool-form disabled="!CurrentUser.canUpdate">
...
</form>
My issue is that I cannot figure out how to pass both form and CoolFormCtrl through the link function.
If I comment out the require:'form' line then WhatsThis = CoolFormCtrl:
With the require:'form' line uncommented WhatsThis = coolForm
And when trying to pass a 5th parameter WhatsThis = coolForm and AndThis = undefined
controller: 'CoolFormCtrl',
require: 'form',
link: function(scope, elem, attrs, WhatsThis, AndThis) {
$log.log(WhatsThis);
$log.log(AndThis);
Is there any way to pass both a controller and required form to a directives link function?
Try:
angular.module('cool').directive('formNavHandler', [
'$log', function($log) {
return {
restrict: 'A',
scope: {
disabled: '=coolFormDisabled'
},
require: ['formNavHandler', 'form'],
controller: 'CoolFormCtrl',
link: function(scope, elem, attrs, WhatsThis) {
$log.log(WhatsThis);
...
}
};
}]);
WhatsThis will be an array of controllers.

How to make two way binding between angular directives without using "dots"

Suppose, I have controller:
angular.module('tf').controller('Ctrl', function($scope){
$scope.params = {
orderBy: null
};});
And a directive "common":
angular.module('tf').directive("common", function() {
return {
restrict: 'E',
replace: true,
template: '<div><outer order-by="orderBy"><inner order-by-field="name1"></inner><inner order-by-field="name2"></inner></outer></div>',
controller: function ($scope) {
},
scope: {
orderBy: '='
},
link: function (scope, element, attrs) {
}
}});
Controller is using directive within it's template:
<div ng-app="tf">
<div ng-controller="Ctrl">
<common order-by="params.orderBy"></common>
<div style="color:red">{{params.orderBy}}</div>
</div>
This directive is using directive "outer":
angular.module('tf').directive("outer", function() {
return {
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-transclude></div>',
controller: function ($scope) {
this.order = function (by) {
$scope.orderBy = by
};
},
scope: {
orderBy: '=',
}
}});
Which is parent for the directive "inner":
angular.module('tf').directive("inner", function() {
return {
require: '^outer',
restrict: 'E',
transclude: true,
replace: true,
template: '<div ng-click="onClicked()">{{orderByField}}</div>',
controller: function ($scope) {
$scope.onClicked = function () {
$scope.outer.order($scope.orderByField);
}
},
scope: {
orderByField: '#'
},
link: function (scope, element, attrs, outer) {
scope.outer = outer;
}
}});
The directive "outer" shares "order" method with directive "inner" by it's controller. The directive "inner" is accessing it by using "require" mechanism.
For some reason, this is not working as expected (Property of the controller isn't updated each time it's changed by directive). If I place "orderBy" into object (e.g. {"order": {"by": null }} ) and use object instead of string value, everything is working as expected ( controller scope is properly updated by the directive). I know about "always use dots" best practices principle, but I don't wanna use it here, because it would make my directive's API less intuitive.
Here is jsfiddle:
http://jsfiddle.net/A8Vgk/1254/
Thanks

error while passing data to directive in Angular

I have this simple directive:
angular.module('app')
.directive('ppBubble', function () {
function PpBubble($scope, $element, $attrs) {
var vm = this;
vm.message = $attrs.message;
vm.isSent = $attrs.sent;
}
return {
templateUrl: '../views/directives/ppBubble.html',
restrict: 'E',
controller: PpBubble,
controllerAs: 'vm',
bindToController: true
};
});
And here is how I use it:
<pp-bubble ng-repeat="msg in vm.messages" sent="{{msg.sent}}" message="{{msg.message}}"></pp-bubble>
The problem is that I see this "msg.message" instead of the string this object attribute hold:
Here is the messages array:
vm.messages = [
{
sent: true,
message: "aasdasd dsfsdfd"
}, ...
]
I am probably missing something stupid :(
The problem is that you're doing vm.message = $attrs.message; and you're taking the string literally, which means: {{msg.message}}, since that's the text that's written in that attribute:
message="{{msg.message}}".
You need to work with a scope to make Angular work its binding magic, and take the data from there:
function PpBubble($scope, $element, $attrs) {
var vm = this;
vm.message = $scope.message;
vm.isSent = $scope.sent;
}
return {
templateUrl: '../views/directives/ppBubble.html',
restrict: 'E',
scope: { message: "=", sent: "=" },
controller: PpBubble,
controllerAs: 'vm',
bindToController: true
};
Here is the messages array:
vm.messages = [
{
sent: true,
message: "aasdasd dsfsdfd"
}, ...
]
EDIT: Another thing is that now that you're binding from the directive, you need to bind to the variable itself, and not to its string literal, meaning to remove the {{}} from the directive HTML:
<pp-bubble ng-repeat="msg in messages" sent="msg.sent" message="msg.message"></pp-bubble>
Fiddle
Here is what worked for me:
function PpBubble($scope, $element, $attrs) {
var vm = this;
}
return {
templateUrl: '../views/directives/ppBubble.html',
restrict: 'E',
controller: PpBubble,
controllerAs: 'vm',
bindToController: true
};
});
Also i removed the {{}} from the html:
Thanks to #Omri Aharon

why can not the controller of the required directive be found in angularjs?

I wrote two directives in angularjs, one is embedded in the other one.
below is the scripts for the directives:
module.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
this.scope = $scope;
return $scope.panes = [];
},
link: function(scope, element, attrs) {
return $log.log('test1', scope.panes);
}
};
}
]);
module.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
controller: function($scope) {
return this.x = 1;
},
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, fooCtrl) {
return $log.log('test2', fooCtrl);
}
};
}
]);
below is the piece of html:
<foo ng-controller="IndexController">
<bar></bar>
</foo>
below is the element generated, inspected from the chrome developer tools
<div id="test" ng-transclude="" ng-controller="IndexController" class="ng-scope">
<div ng-transclude="" class="ng-scope"></div>
</div>
below is chrome console output:
test2
Array[0]
length: 0
__proto__: Array[0]
angular.js:5930
test1
Array[0]
length: 0
__proto__: Array[0]
Question:
The child directive can not get the parent directive's controller, so the fourth parameter "fooCtrl" for link function of "bar" is an empty array. what's the thing that I do it wrong?
update and answer:
At last I found the reason that made the weird result. It's just a stupid mistake that I have made:
// in directive "foo"
controller: function($scope) {
this.scope = $scope;
// This line below is wrong. It is here
// because I transcompiled coffeescript to js.
// return $scope.panes = [];
// It should be like below:
$scope.panes = []
// I should have written .coffee like below
// controller: ($scope) ->
// #scope = $scope
// $scope.panes = []
// return # prevent coffeescript returning the above expressions.
// # I should rather have added the above line
}
After correcting the mistake, I tried and found there's nothing to prevent using controller or providing empty content in child directives.
AFAIK, you cannot have a controller in the child directive.
Demo: http://plnkr.co/edit/kv9udk4eB5B2y8SBLGQd?p=preview
app.directive('foo', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
template: '<div id="test" ng-transclude></div>',
controller: function($scope) {
$scope.panes = ['Item1','Item2','Item3']
return {
getPanes: function() { return $scope.panes; }
};
},
link: function(scope, element, attrs, ctrl) {
$log.log('test1', ctrl, ctrl.getPanes(), scope.panes);
}
};
}
]);
I removed the child controller.
app.directive('bar', [
'$log', function($log) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?foo',
template: '<div ng-transclude></div>',
link: function(scope, element, attrs, ctrl) {
scope.x = 1;
$log.log('test2', ctrl, ctrl.getPanes(), scope.panes, scope.x);
}
};
}
]);

Categories