change directive when scope variable changes - javascript

I am using http request to get data from json file which I than use in controller.
app.controller('mainCtrl', ['$scope', 'loaderService', function ($scope, loaderService) {
//gets data from service
loaderService.getLoadedHtml().then(function (result) {
$scope.fields = result.data;
});
}]);
I need to update directive when this $scope.fields change as
app.directive('dform', function () {
return {
scope: {
action: '#',
method: '#',
html: '='
},
link: function (scope, elm, attrs) {
var config = {
"html": scope.fields
};
scope.$watch('fields', function (val) {
elm.dform(config);
});
//console.log(config);
//elm.dform(config);
}
};
})
and here is how I am using this directive
<div html="fields" dform></div>
But in my case when $scope.fields changes, i get scope as undefined in my directive $watch function.
Question:
How can I get the updated value for scope.fields in scope.$watch function?

You need to give the directive access to fields by adding a binding for it:
scope: {
action: '#',
method: '#',
html: '=',
fields: '='
}
And HTML:
<dform fields="fields" ...
The value might be undefined the first time, then you don't want to call dform:
scope.$watch('fields', function(newValue, oldValue) {
if (newValue === oldValue) return;
var config = {
"html": newValue
};
elm.dform(config);
});
Update
With this HTML:
<div html="fields" dform></div>
You just need to watch html instead, no need for $parent or adding fields as a binding:
scope.$watch('html', ...

Usually for directives that should be as transparent as possible, no new scope is supposed be used. Having a new scope also prevents other directives from requesting a new scope on the same element.
If only one of the attributes is supposed to be dynamic, it is as simple as
scope: false,
link: function (scope, elm, attrs) {
scope.$watch(function () { return scope[attrs.html] }, function (val) {
if (val === undefined) return;
var config = {
action: attrs.action,
method: attrs.method,
html: val
};
elm.dform(config);
});
}
Alternatively, bindToController can be used in more modern, future-proof fashion (depending on what happens with html, $scope.$watch can be further upgraded to self.$onChanges hook).
scope: true,
bindToController: {
action: '#',
method: '#',
html: '='
},
controller: function ($scope, $element) {
var self = this;
$scope.$watch(function () { return self.html }, function (val) {
if (val === undefined) return;
var config = {
action: attrs.action,
method: attrs.method,
html: val
};
$element.dform(config);
});
}
Considering that html="fields", the code above will watch for fields scope property.

use $parent.fields instead of fields

Related

How can i update a directive $scope from other directive contoller in Angular.js?

I have a diretive with a list of events loading from my service service:
.directive('appointments', [function () {
return {
restrict: 'CE',
scope: {
ngTemplate: '=',
},
controller: ['$scope','calendarService', function ($scope, calendarService) {
var vm = this;
vm.events = calendarService.getEvents();
}],
controllerAS:'vm',
link: function (scope, elem, attrs) {
scope.getTemplateUrl = function () {
if (angular.isDefined(scope.ngTemplate))
return scope.ngTemplate;
else
return "/list.directive.html";
}
},
template: '<div ng-include="getTemplateUrl()"></div>'
}
}])
Now in another directive i am updating this list, how can i update the list in the first controller?
.directive('appointmentsUpdate', [function () {
return {
restrict: 'CE',
scope: {
ngEventId: '=',
},
controller: ['$scope','calendarService', function ($scope, calendarService) {
var vm = this;
vm.update = calendarService.updateEvent(scope.ngEventId).then(function(res){
// Here is where the newly created item, should be added to the List (vm.events) from first directive
)
});
}],
controllerAS:'vm',
link: function (scope, elem, attrs) {
scope.getTemplateUrl = function () {
if (angular.isDefined(scope.ngTemplate))
return scope.ngTemplate;
else
return "/list.directive.html";
}
},
template: '<div ng-include="getTemplateUrl()"></div>'
}
}])
you can use angular broadcast service for this:
in first directive use this:
$rootScope.$broadcast('greeting', data_needs_to_be_send);
in other directive listen the event to update its scope:
$scope.$on('greeting', listenGreeting)
function listenGreeting($event, message){
alert(['Message received',message].join(' : '));
}
We use require property to make communication between directives.
Something like this
return {
restrict: 'AE',
require: '^ParentDirective or ^SameLevelDirective'
}
Here is the clear explanation of Driectives That Communicate by ToddMotto
Services are singletons, so if you update the list from one place (with your calendarService.updateEvent()), then if you retrieve the data from the service in the other directive, it should be the updated list.
You could use a watch to check when the list is updated:
$scope.$watch(() => calendarService.getEvents(), (newValue, oldValue) => {
// update your scope with the new list
}, true);

Call function in Directive when Parent Scope Variable Changes

I need to call a function in my directive when the value of variable in the parent controller changes. I tried adding a watch (I'm obviously doing it wrong) because nothing happens when the value changes. Here is the directive:
angular.module('ssq.shared').directive('checkboxPicklist', function() {
return {
restrict: 'E',
templateUrl: '/Scripts/app/Shared/directives/checkboxPicklist.html',
replace: true,
scope: {
itemId: '=',
list: '=',
nameProp: '=',
title: '#',
searchPlaceholder: '#',
callbackFn: '&',
callMore: '&',
clear: '='
},
link: function (scope, element, attrs) {
scope.query = '';
var parent = scope.$parent;
var clear = parent.clear;
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
var child = element.find('.dropdown-menu');
child.on({
'click': function (e) {
e.stopPropagation();
}
});
var selectedItemFn = function (item) {
return item.selected;
};
scope.getSelectedCount = function () {
return _.filter(scope.list, selectedItemFn).length;
};
scope.loadMore = function () {
scope.callMore();
};
scope.allSelected = function(list) {
var newValue = !scope.allNeedsMet(list);
_.each(list, function(item) {
item.selected = newValue;
scope.callbackFn({ object: item });
});
};
scope.allNeedsMet = function(list) {
var needsMet = _.reduce(list, function(memo, item) {
return memo + (item.selected ? 1 : 0);
}, 0);
if (!list) {
return (needsMet === 0);
}
return (needsMet === list.length);
};
function clearAll() {
_.each(list, function (item) {
item.selected = false;
})
}
}
};
});
Here is where I am trying to watch the variable:
var parent = scope.$parent;
var clear = parent.clear;
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
Here is the function in my parent controller that changes the value of "clear"
$scope.clearFilters = function (clear) {
$scope.clear = true;
$scope.form.selected.services = [];
$scope.form.picked.areas = [];
$scope.form.certified.verifications = [];
$scope.form.subscribed.subscriptions = [];
$scope.form.OperatorBusinessUnitID = null;
$scope.form.OperatorBusinessUnitID = null;
};
I tried setting an attribute called "clearFilter" and assigning the variable to it, but the watch still doesn't trigger:
scope.$watch(attrs.clearFilter, function (value) {
if (value == true) {
this.clearAll();
}
});
<checkbox-picklist data-item-id="'servicesPicklist'"
data-search-placeholder="Search Services"
data-list="services"
data-title="Service(s)"
data-name-prop="'vchDescription'"
data-callback-fn="addService(object)"
call-more="loadMoreServices()"
clear-filter="clear">
</checkbox-picklist>
I'm not really sure if I am calling the function correctly. scope.$parent above does get the initial value of the variable from the parent scope, but once it changes, it never updates.
EDIT:What I have discovered is the normal scope.$watch('clear', function...) is not working it seems because the directive is in "ssq.shared" module which is injected in my my Main Module "myModule" (see below), so even though the page the directive is on uses my 'GeneralSearchCtrl', I cannot get the watch to work on the variable located in 'GeneralSearchCtrl'. If I use scope.$parent.clear I can see the value of the variable, but I cannot seem to set a watch on it.
My module injection code:
var app = angular.module('myModule', ['ui.bootstrap', 'checklist-model', 'ssq.shared', 'ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.pagination', 'ui.grid.selection', 'ui.grid.exporter', 'ui.grid.autoResize', 'ui.router', 'cgBusy', 'ui.mask', 'ngFileUpload', 'ngSanitize']);
The page where the directive lives uses:
<div ng-app="myModule" ng-controller="GeneralSearchCtrl">
I am unable to get a watch on the variable located in GeneralSearchCtrl.
Any assistance is greatly appreciated!!!!
Add a watch for the $scope value and call the function,
scope.$watch('clear', function(newValue, oldValue) {
if (newValue) {
this.clearAll();
}
});
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
This.clearAll() doesn't exist in the scope of your $watch function. Simply calling clearAll() should work better.
The signature of the watch function is not correct.
scope.$watch('clear', function (new, old) {}
As it turns out, the problem was that the directive had scope:{...} in its definition which stopped the "normal" scope.$watch('clear', function...) from working. I had to add clear: '=' to the scope list like so:
replace: true,
scope: {
itemId: '=',
list: '=',
nameProp: '=',
title: '#',
searchPlaceholder: '#',
callbackFn: '&',
callMore: '&',
clear: '='
},
Then clear="clear" to the directive like so:
<checkbox-picklist data-item-id="'servicesPicklist'"
data-search-placeholder="Search Services"
data-list="services"
data-title="Service(s)"
data-name-prop="'vchDescription'"
data-callback-fn="addService(object)"
call-more="loadMoreServices()"
clear="clear">
</checkbox-picklist>
Then in the directive I had to add the watch like this for it work:
scope.$watch('$parent.clear', function (newValue, oldValue) {
if (newValue == true) {
clearAll();
alert('it works!');
}
})
I really hope this helps someone else as this was difficult for me to figure out. Happy coding!

How to get isolated scope of a directive with ngrepeat and two way bind?

How we can get particular isolated scope of the directive while calling link function from controller(parent)?
I am having a directive and repeating it using ng-repeat. Whenever a button in the directive template is clicked it will call a function- Stop() in directive controller which in-turn calls function test() in parent controller, inside test() it will call a method dirSample () in directive's link function.
When I print the scope inside dirSample(), it prints the scope of the last created directive not the one which called it.
How can I get the scope of the directive which called it?
Find the pluker here
.directive('stopwatch', function() {
return {
restrict: 'AE',
scope: {
meri : '&',
control: '='
},
templateUrl: 'text.html',
link: function(scope, element, attrs, ctrl) {
scope.internalControl = scope.control || {};
scope.internalControl.dirSample = function(){
console.log(scope)
console.log(element)
console.log(attrs)
console.log(ctrl)
}
},
controllerAs: 'swctrl',
controller: function($scope, $interval)
{
var self = this;
self.stop = function()
{
console.log($scope)
$scope.meri(1)
};
}
}});
full code in plunker
I've changed the binding of your function from & to = since you need to pass a parameter. This means some syntax changes are in order, and also you need to pass the scope along the chain if you want to have it all the way at the end:
HTML:
<div stopwatch control="dashControl" meri="test"></div>
Controller:
$scope.test = function(scope)
{
console.log(scope);
$scope.dashControl.dirSample(scope);
}
Directive:
.directive('stopwatch', function() {
return {
restrict: 'AE',
scope: {
meri : '=',
control: '='
},
templateUrl: 'text.html',
link: function(scope, element, attrs, ctrl) {
scope.internalControl = scope.control || {};
scope.internalControl.dirSample = function(_scope){
console.log(_scope);
}
},
controllerAs: 'swctrl',
controller: function($scope, $interval)
{
var self = this;
self.stop = function()
{
console.log($scope);
$scope.meri($scope);
};
}
}});
Plunker

$scope.$watch doesn't fire when I update from a directive

I have the following code snippets:
HTML:
<div data-filedrop data-ng-model="file"></div>
Controller:
$scope.$watch('file', function(newVal) {
if (newVal) {
alert("File",newVal);
}, false);
}
Directive:
angular.module('app').directive('filedrop', function () {
return {
restrict: 'A',
templateUrl: './directives/filedrop.html',
replace: true,
scope: {
ngModel: '=ngModel'
},
link: function (scope, element) {
var dropzone = element[0];
dropzone.ondragover = function () {
this.className = 'hover';
return false;
};
dropzone.ondragend = function () {
this.className = '';
return false;
};
dropzone.ondrop = function (event) {
event.preventDefault();
this.className = '';
scope.$apply(function () {
scope.ngModel = event.dataTransfer.files[0];
});
return false;
};
}
};
});
The $watch function is never triggered when I update the $scope.
Any Ideas?? Might be an isolated scope issue? It used to work until yesterday... when I had to redo
bower install && npm install
I can confirm:
dropzone.ondrop is fired
event.dataTransfer.files[0] does contain the file being dropped
because of the bower install I also tried angular 2.1.14, 2.1.15 and 2.1.16 (current) but none are working
Thanks!
Sander
ngModel is a controller/provider, it's not a scope. It's not identical to using a scope like in a controller in any way whatsoever. You have to use ngModel.$setViewValue('some value') to manipulate the value. You also have to add the ngModel like this:
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
// do some stuff
ngModel.$setViewValue(element.html()); // example
}
I found a good tutorial which describes this perfectly: http://suhairhassan.com/2013/05/01/getting-started-with-angularjs-directive.html#.U1jme-aSzQ4
Another option would of course be to just pass a scope variable like this:
Directive:
scope: {
'someAttribute': '='
},
link: function(scope, element) {
dropzone.ondrop = function(event) {
scope.$apply(function() {
scope.someAttribute = event.dataTransfer.files[0];
});
}
}
Controller View:
<div filedrop some-attribute="mymodel"></div>
Controller:
$scope.$watch('mymodel', function(newVal) {
// yeah
});
It seems that you are not modifying the value of scope.ngModel. Instead you are overwriting variable scope.ngModel so that it points to the different object, namely: event.dataTransfer.files[0]

Angular Directive refresh on parameter change

I have an angular directive which is initialized like so:
<conversation style="height:300px" type="convo" type-id="{{some_prop}}"></conversation>
I'd like it to be smart enough to refresh the directive when $scope.some_prop changes, as that implies it should show completely different content.
I have tested it as it is and nothing happens, the linking function doesn't even get called when $scope.some_prop changes. Is there a way to make this happen ?
Link function only gets called once, so it would not directly do what you are expecting. You need to use angular $watch to watch a model variable.
This watch needs to be setup in the link function.
If you use isolated scope for directive then the scope would be
scope :{typeId:'#' }
In your link function then you add a watch like
link: function(scope, element, attrs) {
scope.$watch("typeId",function(newValue,oldValue) {
//This gets called when data changes.
});
}
If you are not using isolated scope use watch on some_prop
What you're trying to do is to monitor the property of attribute in directive. You can watch the property of attribute changes using $observe() as follows:
angular.module('myApp').directive('conversation', function() {
return {
restrict: 'E',
replace: true,
compile: function(tElement, attr) {
attr.$observe('typeId', function(data) {
console.log("Updated data ", data);
}, true);
}
};
});
Keep in mind that I used the 'compile' function in the directive here because you haven't mentioned if you have any models and whether this is performance sensitive.
If you have models, you need to change the 'compile' function to 'link' or use 'controller' and to monitor the property of a model changes, you should use $watch(), and take of the angular {{}} brackets from the property, example:
<conversation style="height:300px" type="convo" type-id="some_prop"></conversation>
And in the directive:
angular.module('myApp').directive('conversation', function() {
return {
scope: {
typeId: '=',
},
link: function(scope, elm, attr) {
scope.$watch('typeId', function(newValue, oldValue) {
if (newValue !== oldValue) {
// You actions here
console.log("I got the new value! ", newValue);
}
}, true);
}
};
});
I hope this will help reloading/refreshing directive on value from parent scope
<html>
<head>
<!-- version 1.4.5 -->
<script src="angular.js"></script>
</head>
<body ng-app="app" ng-controller="Ctrl">
<my-test reload-on="update"></my-test><br>
<button ng-click="update = update+1;">update {{update}}</button>
</body>
<script>
var app = angular.module('app', [])
app.controller('Ctrl', function($scope) {
$scope.update = 0;
});
app.directive('myTest', function() {
return {
restrict: 'AE',
scope: {
reloadOn: '='
},
controller: function($scope) {
$scope.$watch('reloadOn', function(newVal, oldVal) {
// all directive code here
console.log("Reloaded successfully......" + $scope.reloadOn);
});
},
template: '<span> {{reloadOn}} </span>'
}
});
</script>
</html>
angular.module('app').directive('conversation', function() {
return {
restrict: 'E',
link: function ($scope, $elm, $attr) {
$scope.$watch("some_prop", function (newValue, oldValue) {
var typeId = $attr.type-id;
// Your logic.
});
}
};
}
If You're under AngularJS 1.5.3 or newer, You should consider to move to components instead of directives.
Those works very similar to directives but with some very useful additional feautures, such as $onChanges(changesObj), one of the lifecycle hook, that will be called whenever one-way bindings are updated.
app.component('conversation ', {
bindings: {
type: '#',
typeId: '='
},
controller: function() {
this.$onChanges = function(changes) {
// check if your specific property has changed
// that because $onChanges is fired whenever each property is changed from you parent ctrl
if(!!changes.typeId){
refreshYourComponent();
}
};
},
templateUrl: 'conversation .html'
});
Here's the docs for deepen into components.

Categories

Resources