I would like to do something like this
fiddle, making the text disappear and reappear with every click.
Problem is, it seem that with an isolated scope you can't have access to the controller scope. I solved it in the link function, handling there click event and setting my "showMe" flag using scope.$apply, like:
scope.$apply('showMe = false');
Is this the right way to go or there is some more elegant method?
Here you go (http://jsfiddle.net/66d0t7k0/1/)
Put your click handler in the link function and expose showMe to the scope
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-click=\"clickMe()\">Click me</button>',
scope: {
exampleAttr: '#'
},
link: function (scope) {
scope.clickMe = function () {
scope.showMe = !scope.showMe;
};
}
};
});
To expand on apairet's answer, since your directive is using an isolated scope, you could handle all of that in the template itself like so:
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-init=\"showMe = false\" ng-click=\"showMe = !showMe\">Click me</button>',
scope: {
exampleAttr: '#'
},
link: function (scope) {
}
};
});
Another consideration is to use ng-if rather than ng-show as it doesn't render the element in the DOM until the expression evaluates to true.
You can hide the scope in the directive by setting scope: false
You can then put all your function in the main controller scope
angular.module('appMyApp').directive('appMyAppItem', function() {
return {
transclude: true,
templateUrl: 'link/to/url',
controller: 'appMyAppController',
scope: false
}
});
angular.module('appMyApp').controller('appMyAppController', ['$scope', function($scope){
$scope.showItem = true;
$scope.toggleItem = function(){
$scope.showItem = !$scope.showItem;
};
}]);
Hope this helps
Related
I have a pretty simple directive, which basically contains child directives, which each call a function when clicked.
<yd-chart-selector>
<yd-chart-selector-item button-class="column" on-click="main.drawColumnChart()"></yd-chart-selector-item>
<yd-chart-selector-item button-class="web" on-click="main.drawSpiderChart()"></yd-chart-selector-item>
<yd-chart-selector-item button-class="line" on-click="main.drawLineChart()"></yd-chart-selector-item>
</yd-chart-selector>
getting this directive to work in it's simplest form, ie clicking an item calls the method, was simple to do.
i also wanted to set the clicked yd-chart-selector-item to active. again, doing that was simple, using a property on my VM.
however, when I set an item to active, the other items should become inactive.
so I figured that I would maintain a list of items on the parent, and use the parent to handle invoking the actions, and clearing the active of the items in it's list.
I have got this working, but it doesnt feel right to me.
app.directive('ydChartSelector', chartSelector);
function chartSelector() {
var directive = {
template: '<div class="form-group graph-type-select pull-right" ng-transclude=""></div>',
restrict: 'E',
bindToController: true,
link: link,
controller: controller,
controllerAs: 'vm',
transclude: true
};
return directive;
function controller() {
var vm = this;
vm.items = [];
vm.addItem = addItem;
vm.selectItem = selectItem;
function addItem(item) {
vm.items.push(item);
if(vm.items.length === 1) {
item.selected = true;
}
}
function selectItem(item) {
resetUi(vm);
item.selected = true;
item.onClick();
}
}
function resetUi(vm) {
vm.items.forEach(function(value) {
value.selected = false;
});
}
}
app.directive('ydChartSelectorItem', chartSelectorItem);
function chartSelectorItem() {
var directive = {
template: '<button type="button" class="btn graph-type-btn" ng-class="[vm.buttonClass, {selected: vm.selected}]" ng-click="vm.f(this)"></button>',
restrict: 'E',
link: link,
bindToController: true,
controller: controller,
controllerAs: 'vm',
require: '^ydChartSelector',
scope: {
onClick: '#',
buttonClass: '#'
}
};
return directive;
function link(scope, element, attrs, chartSelectorController) {
chartSelectorController.addItem(scope.vm);
}
function controller() {
var vm = this;
vm.selected = false;
vm.f = f;
function f($scope) {
$scope.$parent.$parent.vm.selectItem(vm);
}
}
}
the part that is bugging me is:
function f($scope) {
$scope.$parent.$parent.vm.selectItem(vm);
}
there must be a better way to do this?
I tried using '&' and '#' combinations for trying to bind to the parent, but could not get it right.
You should use isolated scope with & for passing method to directive & method should have on-click="main.drawLineChart()" function bracket so that the method will get call, Inside you controller you could call vm.onClick() to call method of directive element.
scope: {
onClick: '&',
buttonClass: '#'
}
Markup
<yd-chart-selector>
<yd-chart-selector-item button-class="column" on-click="main.drawColumnChart">
</yd-chart-selector-item>
<yd-chart-selector-item button-class="web" on-click="main.drawSpiderChart()">
</yd-chart-selector-item>
<yd-chart-selector-item button-class="line" on-click="main.drawLineChart()">
</yd-chart-selector-item>
</yd-chart-selector>
Here is a directive that is loading new Template from file:
.directive('candidatesFilter', function(){
return {
resctict: 'E',
replace: true,
templateUrl: 'views/directives/filters/AAAA.html'
}
})
Next HTML-element calls this directive from the other HTML-Template (e.g. xxx.html):
<candidates-filter></candidates-filter>
There is next controller for this parent Template (xxx.html):
app.controller('candidatesController', function($scope, $location ){
$scope.addPeson = function() {
$location.url('/candidate/0');
};
});
Method addPerson() is not accessible inside the Directive's template AAAA.html, because
data-ng-click="addPerson()"
is not working there. How to change the Directive to make addPerson() method available inside the directive's template?
TEMPORARY Solution
I fixed this issue by next solution
.directive('candidatesFilter', function(){
return {
resctict: 'E',
replace: true,
templateUrl: 'views/directives/filters/AAAA.html',
controller: function(){
$('button.add').on('click',function(){
location.hash = '#/candidate/0';
});
}
}
})
If I understand the problem correctly:
You can pass a function into the directive for it to use
<candidates-filter></candidates-filter>
becomes
<candidates-filter add-candidate="addPerson()"></candidates-filter>
and the directive definition changed as follows:
.directive('candidatesFilter', function() {
return {
resctict: 'E',
replace: true,
scope: {
addCandidate: '&addCandidate'
}
templateUrl: 'views/directives/filters/AAAA.html'
link: function(scope, element, attrs) {
scope.someFunctionInDirective = function() {
scope.addCandidate();
}
};
}
})
Alternatively you can call it with the ng-click like normal from the button
Hope this helps clarify it?
I have a controller-value that is bound to a value in my directive with "=".
But when i update the value in the directive, and then call a function in the controller, the controller-value does not get updated before det controller function is executed.
This is how i implemented it:
HTML:
<my-directive page="searchCriterias.page" search-parent="search()" ></my-directive>
JavaScript:
app.controller('MainCtrl', function ($scope) {
$scope.searchCriterias: {page: 1};
$scope.search = function() {
SearchService.findCasesByCriteria($scope.searchCriterias).then(function (res) {
//show search result
}, function (e) {
});
}
});
app.directive('myDirective', function () {
return {
restrict: 'E',
template: "<div><button ng-click='nextPage()'>next</button></div>",
replace: true,
scope: { page: '=', searchParent: '&searchParent' }
},
link: function ($scope, element, attr) {
$scope.nextPage(){
$scope.page++;
$scope.searchParent();
}
}
}
)
The problem is that page is not updated before search function executed, so findCasesByCriteria is being excecuted with wrong page number. (If i ad timeout before calling $scope.searchParent() in the directive, the value gets updated before the controller-function gets called)
I think your problem is due to you are in a child scope. See here https://egghead.io/lessons/angularjs-the-dot for more infos.
Can you try with this :
<my-directive search-criterias="searchCriterias" search-parent="search()" ></my-directive>
and :
app.directive('myDirective', function () {
return {
restrict: 'E',
template: "<div><button ng-click='nextPage()'>next</button></div>",
replace: true,
scope: { searchCriterias: '=', searchParent: '&searchParent' },
link: function ($scope, element, attr) {
$scope.nextPage(){
$scope.searchCriterias.page++;
$scope.searchParent();
}
}
}
);
There are two ideas that come to me straight away.
You could pass the controller function to the isolate scope using '&' instead of '='. By doing this you could call the function from the directive confident that the value has changed
Otherwise you could set a $watch on the variable in the controller that would call the function when the value changes
I think no need to do anything. You just need to add $.apply() function before calling the function for directive.
app.directive('myDirective', function () {
return {
restrict: 'E',
template: "<div><button ng-click='nextPage()'>next</button></div>",
replace: true,
scope: { page: '=', searchParent: '&searchParent' }
},
link: function ($scope, element, attr) {
$scope.nextPage(){
$scope.page++;
$scope.$apply();
$scope.searchParent();
}
}
}
)
That's it It should work!!
Is it possible to "watch" for ui changes on the directive?
something like that:
.directive('vValidation', function() {
return function(scope, element, attrs) {
element.$watch(function() {
if (this.hasClass('someClass')) console.log('someClass added');
});
}
})
Yes. You can use attr.$observe if you use interpolation at the attribute.
But if this is not an interpolated attribute and you expect it to be changed from somewhere else in the application (what is extremely not recommended, read Common Pitfalls), than you can $watch a function return:
scope.$watch(function() {
return element.attr('class');
}, function(newValue){
// do stuff with newValue
});
Anyway, its probably that the best approach for you would be change the code that changes the element class. Which moment does it get changed?
attrs.$observe('class', function(val){});
You can also watch variable in the controller.
This code automatically hides notification bar after some other module displays the feedback message.
HTML:
<notification-bar
data-showbar='vm.notification.show'>
<p> {{ vm.notification.message }} </p>
</notification-bar>
DIRECTIVE:
var directive = {
restrict: 'E',
replace: true,
transclude: true,
scope: {
showbar: '=showbar',
},
templateUrl: '/app/views/partials/notification.html',
controller: function ($scope, $element, $attrs) {
$scope.$watch('showbar', function (newValue, oldValue) {
//console.log('showbar changed:', newValue);
hide_element();
}, true);
function hide_element() {
$timeout(function () {
$scope.showbar = false;
}, 3000);
}
}
};
DIRECTIVE TEMPLATE:
<div class="notification-bar" data-ng-show="showbar"><div>
<div class="menucloud-notification-content"></div>
I'm trying to acheive databinding to a value returned from a service inside a directive.
I have it working, but I'm jumping through hoops, and I suspect there's a better way.
For example:
<img my-avatar>
Which is a directive synonymous to:
<img src="{{user.avatarUrl}}" class="avatar">
Where user is:
$scope.user = CurrentUserService.getCurrentUser();
Here's the directive I'm using to get this to work:
.directive('myAvatar', function(CurrentUser) {
return {
link: function(scope, elm, attrs) {
scope.user = CurrentUser.getCurrentUser();
// Use a function to watch if the username changes,
// since it appears that $scope.$watch('user.username') isn't working
var watchUserName = function(scope) {
return scope.user.username;
};
scope.$watch(watchUserName, function (newUserName,oldUserName, scope) {
elm.attr('src',CurrentUser.getCurrentUser().avatarUrl);
}, true);
elm.attr('class','avatar');
}
};
Is there a more succinct, 'angular' way to achieve the same outcome?
How about this ? plunker
The main idea of your directive is like
.directive('myAvatar', function (CurrentUserService) {
"use strict";
return {
restrict: 'A',
replace: true,
template: '<img class="avatar" ng-src="{{url}}" alt="{{url}}" title="{{url}}"> ',
controller: function ($scope, CurrentUserService) {
$scope.url = CurrentUserService.getCurrentUser().avatarUrl;
}
};
});