As mentioned in the title in my angular application due to the below approach it leads to creation of many watch, i want to find some alternative methods for this.
<div ng-app="myapp">
<first></first>
</div>
var myApp = angular.module('myapp', []);
myApp.directive('first', [
function() {
return {
restrict: 'AE',
replace: true,
transclude: true,
template: '<div id="first"><second id="second" param="paramData"></second></div>',
scope: {
},
controller: [
'$scope',
'$element',
'$attrs',
function($scope, $element, $attrs) {
}
],
link: function(scope, element, attrs, ctrl,$timeout) {
scope.paramData = "Test";
scope.updateParamData = function(){
scope.paramData = "TimeOut";
};
//$timeout(scope.updateParamData,5000);
}
};
}
]);
myApp.directive('second', [
function() {
return {
restrict: 'AE',
replace: true,
template: '<div></div>',
scope: {
param: '=param'
},
controller: [
'$scope',
'$element',
'$attrs',
function($scope, $element, $attrs) {
console.log("inside controller",$scope.param);
}
],
link: function(scope, element, attrs) {
console.log("inside link",scope.param);
scope.$watch(scope.param,function(){
console.log("inside watch",scope.param);
element.innerHTML = scope.param;
});
}
};
}
]);
In the above example the param which is passed from first directive to the second directive is controlled by first directive so the para can change at any time so in the second directive i am using the watch to update the second directive HTML based on the param update.
So now the problem is if i used same kind of approach in my application at many places it leads to multiple watch, so i want to check is this approach is correct or is there is any other alternative approach for this.?
There must be a $watch somewhere to detect the change in the value.
One way to reduce the number of watches is not to use two-way binding scope: {param: "="} in the second directive, and instead use one-way binding of "&".
.directive("second", function(){
return {
scope: { param: "&" }, // this does not create a watch on the parent
template: "<div>{{param()}}</div>" // {{ }} creates a watch
}
})
Of course, you can also explicitly add a $watch in the link/controller (although in your particular example where you use element.innerHTML) it can easier be done with the template approach above):
link: function(scope, element){
scope.$watch(function(){ return scope.param(); },
function(newValue, oldValue){
console.log(newValue, oldValue);
});
}
So, the number of watches is 1 in each case.
I see no way to improve this. Since you need to actually listen for changes for param I do not see a way different from watchers in this case.
Related
This is follow up to these 2 questions:
Pass argument between parent and child directives
Parent directive controller undefined when passing to child directive
I have this part working; however, when the value for ng-disabled for parent directive changes, the child directive values don't get updated.
Please see thin plunkr example.
HTML:
<div ng-app="myApp">
<div ng-controller="MyController">
{{menuStatus}}
<tmp-menu ng-disabled="menuStatus">
<tmp-menu-link></tmp-menu-link>
<tmp-menu-link></tmp-menu-link>
</tmp-menu>
<button ng-click="updateStatus()">Update</button>
</div>
</div>
JavaScript(AngularJS):
angular.module('myApp', [])
.controller('MyDirectiveController', MyDirectiveController)
.controller('MyController', function($scope){
$scope.menuStatus = false;
$scope.updateStatus = function(){
$scope.menuStatus = $scope.menuStatus?false:true;
}
})
.directive('tmpMenu', function() {
return {
restrict: 'AE',
replace:true,
transclude:true,
scope:{
disabled: '=?ngDisabled'
},
controller: 'MyDirectiveController',
template: '<div>myDirective Disabled: {{ disabled }}<ng-transclude></ng-transclude></div>',
link: function(scope, element, attrs) {
}
};
})
.directive('tmpMenuLink', function() {
return {
restrict: 'AE',
replace:true,
transclude:true,
scope:{
},
require:'^^tmpMenu',
template: '<div>childDirective disabled: {{ disabled }}</div>',
link: function(scope, element, attrs, MyDirectiveCtrl) {
console.log(MyDirectiveCtrl);
scope.disabled = MyDirectiveCtrl.isDisabled();
}
};
})
function MyDirectiveController($scope) {
this.isDisabled = function() {
return $scope.disabled;
};
}
How can I detect change in parent directive and pass it to child directive without adding angular watcher.
Solution 1
i've set up a working plnkr here: https://plnkr.co/edit/fsxMJPAc05imhBqefaRk?p=preview
the reason of this behaviour is that tmpMenuLink kept a copy of the value returned from MyDirectiveCtrl.isDisabled(). no watcher is set up , so the only way to resolve this is to manually watch for any changes and then update the field.
scope.$watch(function(){
return MyDirectiveCtrl.isDisabled();
}, function(){
scope.disabled = MyDirectiveCtrl.isDisabled();
})
Solution 2
An alternative without watchers is to pass the reference of an object instead of a primitive type, something like:
$scope.menuStatus = {status: false};
new plnkr here: https://plnkr.co/edit/RGEK6TUuE7gkPDS6ygZe?p=preview
I'm trying to pass a boolean value from my controller into my isolated scope directive. When I console.log(attrs) from the directive's link function, the someBoolean attribute is a string, rendering the actual text "main.bool" instead of a true or false value. When I toggle the boolean value from the outer controller, I want it to be updated in the directive.
https://plnkr.co/edit/80cvLKhFvljnFL6g7fg9?p=preview
app.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
scope: {
someBoolean: '='
},
templateUrl: 'myDirective.html',
link: function(scope, element, attrs) {
console.log(scope);
console.log(attrs);
},
controller: function($scope, $element, $attrs) {
console.log(this);
},
controllerAs: 'directiveCtrl',
bindToController: true
};
});
Controller
app.controller('MainCtrl', function($scope) {
var vm = this;
vm.bool = true;
vm.change = function() {
vm.bool = !vm.bool;
}
});
The template
<div>
Inside directive: {{someBoolean}}
</div>
As you have attached your directive Controller to directiveCtrl instead of mainCtrl, you'll access the variable someBoolean using directiveCtrl.someBoolean.
In this case, change the HTML to:
<div>
Inside directive: {{directiveCtrl.someBoolean}}
</div>
Plunker.
Another solution would be to remove the bindToController property inside your directive. With this, you don't need to use the controller name before the variable. Working Plunker.
Read more about this bindToController feature here.
I'm trying to display the elements of an array using ng-repeat and a directive. The directive part is important to the solution. However the element of the array is not getting bound and displays an empty value.
The fiddle can be found at http://jsfiddle.net/qrdk9sp5/
HTML
<div ng-app="app" ng-controller="testCtrl">
{{chat.words}}
<test ng-repeat="word in chat.words"></test>
</div>
JS
var app = angular.module('app', []);
app.controller("testCtrl", function($scope) {
$scope.chat = {
words: [
'Anencephalous', 'Borborygm', 'Collywobbles'
]
};
});
app.directive('test', function() {
return {
restrict: 'EA',
scope: {
word: '='
},
template: "<li>{{word}}</li>",
replace: true,
link: function(scope, elm, attrs) {}
}
});
OUTPUT
["Anencephalous","Borborygm","Collywobbles"]
•
•
•
Expected output
["Anencephalous","Borborygm","Collywobbles"]
•Anencephalous
•Borborygm
•Collywobbles
Appreciate your help
You didn't bind word.
You have used isolate scope. If you don't bind with it's scope property,it won't work.
scope: {
word: '='
},
Try like this
<test word="word" ng-repeat="word in chat.words"></test>
DEMO
var app = angular.module('dr', []);
app.controller("testCtrl", function($scope) {
$scope.chat= {words: [
'Anencephalous', 'Borborygm', 'Collywobbles'
]};
});
app.directive('test', function() {
return {
restrict: 'EA',
scope: {
word: '='
},
priority: 1001,
template: "<li>{{word}}</li>",
replace: true,
link: function(scope, elm, attrs) {
}
}
});
Your directive needs to run before ng-repeat by using a higher priority, so when ng-repeat clones the element it is able to pick your modifications.
The section "Reasons behind the compile/link separation" from the Directives user guide have an explanation on how ng-repeat works.
The current ng-repeat priority is 1000, so anything higher than this should do it.
I want to recreate nsClick behavior with my directive ( changing priority).
So this is my code:
angular.module('MyApp').directive('nsClickHack', function () {
return {
restrict: 'E',
priority: 100,
replace: true,
scope: {
key: '=',
value: '=',
accept: "&"
},
link: function ($scope, $element, $attrs, $location) {
$scope.method();
}
}
});
and the line I'm trying to bind to:
<li ng-repeat="item in items" ns-click-hack="toggle(); item.action()">
toggle and item.action are from other directives.
Can you point me where I was making mistake?
If you are trying to re-create ng-click, then it's probably better to look at the source of the ngClick directive.
For example, it does not create an isolate scope since only one isolate scope can be created on an element and it tries to be accommodating towards other directives. The alternative is to $parse the attribute value, which is what the built-in implementation is doing.
If you are just creating a "poor's man" version of ngClick, then, sure, you could use a callback function "&" defined on the scope, and invoke it when the element is clicked:
.directive("nsClickHack", function(){
return {
restrict: "A",
scope: {
clickCb: "&nsClickHack"
},
link: function(scope, element){
element.on("click", function(e){
scope.clickCb({$event: e}); // ngClick also passes the $event var
});
}
}
});
The usage is as you seem to want it:
<li ng-repeat="item in items" ns-click-hack="toggle(); item.action()">
plunker
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.