I'm relative new to AngularJS and trying to create a directive for add some buttons. I'm trying to modify the controller scope from inside the directive but I can't get it to work. Here is an example of my app
app.controller('invoiceManagementController', ['$scope', function ($scope) {
$scope.gridViewOptions = {
isFilterShown: false,
isCompact: false
};
}]);
app.directive('buttons', function () {
return {
restrict: 'A',
template: '<button type="button" data-button="search" title="Filter"><i class="glyphicon glyphicon-search"></i></button>',
scope: {
gridViewOptions: '='
},
transclude: true,
link: function (scope, element, attr, ctrl, transclude) {
element.find("button[data-button='search']").bind('click', function (evt) {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown
transclude(scope.$parent, function (clone, scope) {
element.append(clone);
});
});
}
};
});
My HTML like following
{{ gridViewOptions.isFilterShown }}
<div data-buttons="buttons" data-grid-view-options="gridViewOptions"></div>
The scope inside the directive does change but is like isolated, I did try paying with the scope property and transclude but I'm probably missing something, would appreciate some light here
When you modify scope inside of your directive's link function, you are modifying your directive's isolated scope (because that is what you have set up). To modify the parent scope, you can put the scope assignment inside of your transclude function:
transclude(scope.$parent, function (clone, scope) {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown
element.append(clone);
});
Ok finally found a solution for this after some more research today. Not sure if the best solution, but this works so good for now.
app.controller('invoiceManagementController', ['$scope', function ($scope) {
$scope.gridViewOptions = {
isFilterShown: false,
isCompact: false
};
}]);
app.directive('buttons', function () {
return {
restrict: 'A',
template: '<button type="button" data-button="search" data-ng-class="gridViewOptions.isFilterShown ? \'active\' : ''" title="Filter"><i class="glyphicon glyphicon-search"></i></button>',
scope: {
gridViewOptions: '='
},
link: function (scope, element, attr, ctrl, transclude) {
element.find("button[data-button='search']").bind('click', function (evt) {
scope.$apply(function () {
// Set the property to the opposite value
scope.gridViewOptions.isFilterShown = !scope.gridViewOptions.isFilterShown;
});
});
}
};
});
Related
I am trying to write a simple custom directive in Angular that turns a tag into a toggle button (similar to a checkbox). The code I have written so far updates the internal variable (isolated scope) but the two way binding doesn't seem to work. When I click the button, the button toggles (the css class is appearing and disappearing) but myVariable is not updating.
Any help much appreciated!
Usage
<button toggle-button="myVariable">My Button</button>
Directive code
( function() {
var directive = function () {
return {
restrict: 'A',
scope: {
toggleButton: '=checked'
},
link: function( $scope, element, attrs ) {
$scope.$watch('checked', function(newVal, oldVal) {
newVal ? element.addClass ('on') : element.removeClass('on');
});
element.bind('click', function() {
$scope.checked = !$scope.checked;
$scope.$apply();
});
}
};
};
angular.module('myApp')
.directive('toggleButton', directive );
}());
just replace
scope: {
toggleButton: '=checked'
}
to
scope: {
checked: '=toggleButton'
}
Your directive scope is looking for an attribute that doesn't exist.
Try changing:
scope: {
toggleButton: '=checked'
},
To
scope: {
toggleButton: '='
},
The difference is that =checked would look for the attribute checked whereas = will use the same attribute as the property name in the scope object
Will also need to change the $watch but you could get rid of it and use ng-class
As charlietfl said, you don't need that checked variable. You are making changes to it instead of the external variable.
Here is a fixed version:
angular.module('components', [])
.directive('toggleButton', function () {
return {
restrict: 'A',
scope:{
toggleButton:'='
},
link: function($scope, $element, $attrs) {
$scope.$watch('toggleButton', function(newVal) {
newVal ? $element.addClass ('on') : $element.removeClass('on');
});
$element.bind('click', function() {
$scope.toggleButton = !$scope.toggleButton;
$scope.$apply();
});
}
}
})
angular.module('HelloApp', ['components'])
http://jsfiddle.net/b3b3qkug/1/
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
Whats the best way to assign a new value through a directive? A two way databinding.
I have a fiddle here where i tried. http://jsfiddle.net/user1572526/grLfD/2/ . But it dosen't work.
My directive:
myApp.directive('highlighter', function () {
return {
restrict: 'A',
replace: true,
scope: {
activeInput: '='
},
link: function (scope, element, attrs) {
element.bind('click', function () {
scope.activeInput = attrs.setInput
})
}
}
});
And my controller:
function MyCtrl($scope) {
$scope.active = {
value : true
};
}
And my view:
<h1 highlighter active-input="active.value" set-input="false">Click me to update Value in scope: {{active}}</h1>
So what i wanna do is update the scope.active with the given attribute setInput.
Any ideas what I'm doing wrong here?
With element.bind you leave the realm of Angular, so you need to tell Angular that something had happened. You do that with the scope.$apply function:
scope.$apply(function(){
scope.activeInput = attrs.setInput;
});
here is an updated jsfiddle.
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.
I'm trying to use ngRepeat in a directive - but I really don't know how to do it. Am I close?
Mowers is an array in my controller where this directive is used.
.directive('slider', function() {
return function() {
template: '<slider><film><frame ng-repeat="mower in mowers">{{mower.series}}</frame></film></slider>';
replace: true;
scope: true;
link: function postLink(scope, iElement, iAttrs) {
// jquery to animate
}
}
});
Yes, you're close, but the syntax was off. and you need to assign something to mowers on your scope.
.directive('slider', function() {
return {
restrict: 'E', //to an element.
template: '<film><frame ng-repeat="mower in mowers">{{mower.series}}</frame></film>',
replace: true,
scope: true, // creates a new child scope that prototypically inherits
link: function postLink(scope, iElement, iAttrs) {
scope.mowers = [
{ series: 1 },
{ series: 2 },
{ series: 3 }
];
}
}
});
Also, you need to not reference <slider> in itself.
The above code also assumes you've made directives for <film> and <frame>.
Finally, if you wanted to pass the mowers array in from an external scope, you'd change your scope setting to this:
scope: {
mowers: '='
}
And you could then set it like so:
<slider mowers="myMowers"></slider>
where myMowers is a variable on your controller or parent directive's scope.
I hope that helps.