I want to add ngshow in the following custom element in a dynamic way... How to do that?
<toggler on-enable="main.enable()" on-disable="main.disable()">
<div style="width:100px;height:100px;background-color:#2fa">
<on>On state</on>
<off>Off state</off>
</div>
</toggler>
cf.directive('toggler', function () {
return {
restrict: 'AE',
scope: {
state: true,
onEnable: '&',
onDisable: '&'
},
compile: function (elem, attrs) {
var onElem = elem.find('on');
var offElem = elem.find('off');
// WANT TO DO THIS
// onElem.ngShow = 'state';
// offElem.ngShow = '!state';
}
};
});
You're doing it in the wrong way. Don't forget a rule of thumb in AngularJS: avoid DOM manipulation when it's not mandatory.
I guess that <on> and <off> are also custom directives, because you can't simply add tags without any defined behaviour. So, why don't put the ngShow attribute directly in this directives? Then, a directive's controller (see the documentation) will handle the communication between <on>/<off> and <toggler>:
myApp.directive('toggler', function () {
return {
restrict: 'AE',
scope: {
state: '=',
},
controller : [
'$scope',
function ($scope) {
this.isOn = function () {
return $scope.state;
};
},
],
};
});
myApp.directive('on', function () {
return {
restrict: 'AE',
require: '^toggler',
template: '<div ng-show="isOn()" ng-transclude />',
replace: true,
scope: true,
transclude: true,
link : function ($scope, element, attributes, togglerController) {
$scope.isOn = togglerController.isOn;
},
};
});
myApp.directive('off', function () {
return {
restrict: 'AE',
require: '^toggler',
template: '<div ng-hide="isOn()" ng-transclude />',
replace: true,
scope: true,
transclude: true,
link : function ($scope, element, attributes, togglerController) {
$scope.isOn = togglerController.isOn;
},
};
});
Fiddle
This way, you will be able to simply unit test your toggler, and extend his behaviour when needed.
Related
...
...
UPDATE
HTML
<my-directive ng-repeat="item in items = ( data | filter: {isExists: true})">
something
</my-directive>
<my-second-directive counter="{{items.length}}"></my-second-directive>
JS
angular.module('directives')
.directive('myDirective', function () {
...
})
.directive('mySecondDirective', function () {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '#'
},
template: '',
link: function (scope, element, attrs) {
alert(scope.counter);
}
});
Excuse me I've not described my question well. My first directive should be ngRepeated, with filter. But in my second directive, I would like to allow to show a counter, how many first directive is currently instantiated, because the user will be able to add and remove instances. So I would like to get the value of the items.length by fly with the second directive. But the link() method of the second directive is fired prior the ngRepeat, so the value of the counter will be an empty string.
Thanks in advance
UPDATE 2
.directive('cardGroupHeader', function($templateCache){
return {
restrict: 'EA',
transclude: true,
replace: true,
require: '^cardGroup',
scope: {
cbiscounter: '=?',
cbcounter: '=?',
cbisarrow: '#?'
},
template: $templateCache.get('card-group-header-tpl.html'),
link: function(scope, $element, $attrs, cardGroupController) {
scope.rowId = cardGroupController.getCurrentId();
console.log(scope.cbcounter);
scope.toggle = function () {
cardGroupController.toggle(scope.rowId)
}
angular.element(document).ready(function () {
console.log(scope.cbcounter);
});
scope.$watch('scope.cbcounter', function (n, o) {
if(n && n != o) {
console.log(n);
}
});
//scope.cbcounter++;
}
};
})
HTML
<card-group-header cbiscounter="true" cbarrow="true" cbcounter="data.length">Waiting for Approval</card-group-header>
<card-group-row cbCollapsed="false">
<card ng-repeat="approveItem in data = (approveItems | filter: {isApproved: false, isRejected: false})">
TEMPLATE
$templateCache.put('card-group-header-tpl.html', '<div class="card-group-header" ng-click="toggle()"><span ng-transclude></span><span class="card-group-counter" ng-if="cbiscounter">{{cbcounter}}</span></div>');
When I change the data.length to 2, this is transferred well. If I use the data.length the scope.cbcounter is always undefined. In case of 2 I've got it back on the console.log(scope.cbcounter);
The counter: '#' means that you are accepting a string value. If you wanted to pass an expression you could either use:
<my-second-directive counter="{{ items.length }}"></my-second-directive>
Or:
.directive('mySecondDirective', function () {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '=' // Accept two ways binding
},
template: '',
link: function (scope, element, attrs) {
alert(scope.counter);
}
});
EDIT:
I finally quite understand the problem! It's because of attributes are not interpolated until after the link phase. You have two following options:
The first option is wrapping every in the link inside $timeout to take it away from the event loop and be executed after DOM finished manipulating:
.directive('mySecondDirective', function ($timeout) {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '=' // Accept two ways binding
},
template: '',
link: function (scope, element, attrs) {
$timeout(function() {
alert(scope.counter);
});
}
});
Secondly, using $observe:
attrs.$observe('counter', function(value){
console.log(value);
});
or using $watch as #jusopi suggested.
I think this would be what you want.
Html
<div ng-app="testapp" ng-controller="testctrl">
<div ng-repeat="item in filtereditems">
{{item}}
</div>
<testdir counter="filtereditems.length" />
</div>
Javascript
angular.module('testapp', [])
.directive('testdir', function(){
return {
restrict: 'E',
scope:{
counter: '='
},
link: function(scope, element, attrs) {
alert(scope.counter);
}
}
})
.controller('testctrl', function($scope, $filter){
$scope.items = [
{name: 'A', isExists: true},
{name: 'B', isExists: false},
{name: 'C', isExists: true},
{name: 'D', isExists: false}
];
$scope.filtereditems = $filter('filter')($scope.items, {isExists: true});
})
My jsfiddle is here.
In addition to #LVarayut's answer about the scope's binding expression, the reason the alert is undefined is because linking is not part of the $digest cycle. So binding and data hasn't be effected yet (don't quote me on that, it's the best way I could verbalize what I'm showing in the code below).
Instead you need to use a watcher to trigger the alert
link: ($scope, elem, attrs)->
#undefined because linking isn't part of the $digest cycle
#alert $scope.count
$scope.$watch 'count', (n, o)->
if n and n isnt o
true
#alert n
http://plnkr.co/edit/xt95gb3cTXfUEHgpWK1W?p=preview
I have a directive, I want that template to show all the rows from data set
app.directive('exampleDirective', [ 'TestProvider', 'TestFactory', function (TestProvider, TestFactory) {
return {
restrict: 'E',
template: '<tr><td>{{Name}} </td><td>{{Surname}}</td></tr>',
replace: true,
link: function(scope, elm, attrs) {
var dat= TestFactory.dataReturn();
for (var i = 0; i < dat.length; i++) {
scope.Name = dat[i].Name;
scope.Surname = dat[i].Surname;
console.log(dat[i]);
}
// alert("hah");
}
};
}]);
How can I make it repeat like ng-repeat ?
Assuming your service returning a promise. Here is the simple code to repeat your data in table.
app.directive('exampleDirective', [ 'TestProvider', 'TestFactory', function (TestProvider, TestFactory) {
return {
restrict: 'E',
template: '<table ng-repeat="person in persons"><tr><td>{{person.Name }} </td><td>{{person.Surname}}</td></tr></table>',
replace: true,
link: function(scope, elm, attrs) {
TestFactory.dataReturn().then(function(resp){
scope.persons = resp.data;
});
}
};
}]);
Alternatively, you could redefine your element directive to represent a single object, and then ng-repeat the directive itself. Obviously, this would require moving the location where you use the factory. Your application architecture may or may not be able to accommodate this change.
directive:
app.directive('exampleDirective', [function () {
return {
restrict: 'E',
template: '<tr><td>{{person.Name}} </td><td>{{person.Surname}}</td></tr>',
replace: true,
scope: {
person: '='
}
};
}]);
And usage:
<example-directive ng-repeat="data in dataFromFactory" person="data"></example-directive>
For some reason I can't make this work based on the other examples I've seen here on SO.
Here's my directive:
(function () {
angular.module('materialDesign')
.directive('aSwitch', directive);
function directive() {
return {
templateUrl: 'elements/material/switch/switch.html',
transclude: false, // I've tried true here
restrict: 'E',
scope: {
enabled: '=',
toggleState: '=',
},
link: function(scope, element) {
element.on('click touchstart', function() {
scope.toggleState = !scope.toggleState;
});
}
};
}
})();
And the controller scope value that I want to change when toggling the switch/checkbox:
$scope.hideInactive = true;
The html:
<a-switch toggle-state="hideInactive"></a-switch>
and further down in my html page, I have this:
<div ng-show="!hideInactive">
<!-- stuff -->
</div>
EDIT:
This version is "working now", but as soon as I click my switch/checkbox a second time, the element.on fires twice, this flipping my scope value back to what it was.....basically, it's not letting me "un-check" my toggle.
angular.module('material')
.directive('aSwitch', [
'$timeout', function($timeout) {
return {
templateUrl: 'elements/material/switch/switch.html',
transclude: false,
restrict: 'E',
scope: {
enabled: '=',
toggleState: '=',
},
link: function (scope, element) {
element.on('click touchstart', function () {
$timeout(function () {
scope.toggleState.state = !scope.toggleState.state;
scope.$apply();
});
});
}
};
}
]);
EDIT and FINAL SOLUTION:
Here's the updated directive link property that fixed everything. I'd like to add that Oleg Yudovich's answer was also used (passing an object as the property instead of a true/false by itself)
link: function (scope, element) {
element.on('click touchstart', function (event) {
if (event.srcElement && event.srcElement.id && event.srcElement.id === "switch") {
event.stopPropagation();
$timeout(function() {
scope.toggleState.state = !scope.toggleState.state;
});
}
});
}
Try to pass object instead of primitive variable like this:
$scope.hideInactive = {
state: false;
}
html without changes:
<a-switch toggle-state="hideInactive"></a-switch>
in your directive:
scope.toggleState.state = !scope.toggleState.state;
Reed this awesome article: https://github.com/angular/angular.js/wiki/Understanding-Scopes
You need to run digest cycle after changes in scope, because changing scope binding from event will not run angular digest cycle, you need to run it manually by doing scope.$apply()
Directive
(function () {
angular.module('materialDesign')
.directive('aSwitch', directive);
function directive($timeout) {
return {
templateUrl: 'elements/material/switch/switch.html',
transclude: false, // I've tried true here
restrict: 'E',
scope: {
enabled: '=',
toggleState: '=',
},
link: function(scope, element) {
element.on('click touchstart', function() {
$timeout(function(){
scope.toggleState = !scope.toggleState;
});
});
}
};
}
})();
Try below code:
angular.module('material').directive('aSwitch', ['$timeout', function($timeout) {
return {
templateUrl: 'elements/material/switch/switch.html',
transclude: false,
restrict: 'E',
scope: {
enabled: '=',
toggleState: '=',
},
link: function(scope, element) {
element.on('click touchstart', function() {
$timeout(function() {
scope.toggleState.state = !scope.toggleState.state;
scope.$apply();
});
});
}
};
}]);
Let's say I have a directive:
<component>
<img ng-src='{{something}}' />
</component>
defined as:
app.directive("component", function() {
return {
scope: {},
restrict: 'E',
transclude: true,
template: "<a href='' ng-click='MyService.doThings()' ng-transclude></a>"
}
});
Despite all my efforts, I fail to understand how to accomplish two tasks:
How do I access the inner image source path?
How can I pass this path to service MyService? (think of a lightbox wrapper)
Update with solution:
app.directive("component", function(LightboxService) {
return {
restrict: 'E',
transclude: true,
replace: true,
template: "<a href='' ng-click='lb()' ng-transclude></a>",
link: function (scope, element, attrs) {
scope.lb = function () {
var src = $(element).find("img").attr("src");
LightboxService.show(src);
}
}
}
});
You can access the source path either by binding it to your controller scope or from a link method using attributes.
You can not access Service from a template. You should inject your service into a controller and define a function in $scope to call from the template.
Check your directive below:
app.directive("component", function() {
return {
scope: {
ngSrc: "#", //Text Binding
},
controller: function($scope, MyService) {
$scope.doThings = function() {
MyService.doThings();
}
},
restrict: 'E',
transclude: true,
template: "<a href='{{ng-src}}' ng-click='doThings' ng-transclude></a>"
}
});
You can learn more about directives with isolated scope here:
https://umur.io/angularjs-directives-using-isolated-scope-with-attributes/
This should not be too hard a thing to do but I cannot figure out how best to do it.
I have a parent directive, like so:
directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
replace: true,
transclude: true,
template: '
<div class="editable-fieldset" ng-click="edit()">
<div ng-transclude></div>
...
</div>',
controller: ['$scope', function ($scope) {
$scope.edit = ->
$scope.editing = true
// ...
]
};
});
And a child directive:
.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
'<div>
<label>' + attrs.label + '</label>
<p>{{ model.' + attrs.field + ' }}</p>
...
</div>'
},
require: '^editableFieldset'
};
});
How can I easily access the model and editing properties of the parent directive from the child directive? In my link function I have access to the parent scope - should I use $watch to watch these properties?
Put together, what I'd like to have is:
<editable-fieldset model="myModel">
<editable-string label="Some Property" field="property"></editable-string>
<editable-string label="Some Property" field="property"></editable-string>
</editable-fieldset>
The idea is to have a set of fields displayed by default. If clicked on, they become inputs and can be edited.
Taking inspiration from this SO post, I've got a working solution here in this plunker.
I had to change quite a bit. I opted to have an isolated scope on the editableString as well because it was easier to bind in the correct values to the template. Otherwise, you are going to have to use compile or another method (like $transclude service).
Here is the result:
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Ctrl', function($scope) {
$scope.myModel = { property1: 'hello1', property2: 'hello2' }
});
myApp.directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
transclude: true,
replace: true,
template: '<div class="editable-fieldset" ng-click="edit()"><div ng-transclude></div></div>',
link: function(scope, element) {
scope.edit = function() {
scope.editing = true;
}
},
controller: ['$scope', function($scope) {
this.getModel = function() {
return $scope.model;
}
}]
};
});
myApp.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
scope: {
label: '#',
field: '#'
},
template: '<div><label>{{ label }}</label><p>{{ model[field] }}</p></div>',
require: '^editableFieldset',
link: function(scope, element, attrs, ctrl) {
scope.model = ctrl.getModel();
}
};
});
HTML:
<body ng-controller="Ctrl">
<h1>Hello Plunker!</h1>
<editable-fieldset model="myModel">
<editable-string label="Some Property1:" field="property1"></editable-string>
<editable-string label="Some Property2:" field="property2"></editable-string>
</editable-fieldset>
</body>
You can get access to parent controller by passing attribute in child directive link function
link: function (scope, element, attrs, parentCtrl) {
parentCtrl.$scope.editing = true;
}