I read quite a few articles about overriding directive but I didn't find a way to correctly prevent a directive from being executed.
Actually I'm using ionic framework, and I'd to like to prevent the default select directive to be executed since it doesn't work on a Microsoft surface...
Any Idea how to do this ?
Ty.
Well, this is an idea :
module.config(['$provide', function($provide) {
$provide.decorator('selectDirective', function($delegate) {
return {};
});
}])
Although it would break the native < select > element, so you might want to disable parts of your directives like this:
module.config(['$provide', function($provide) {
$provide.decorator('selectDirective', function($delegate) {
var directive = $delegate[0];
directive.template = '';
return directive;
});
}])
Related
I am using directives to create a component library in AngularJS 1.5. Hence, my directives need to have isolate scopes.
Some of my directives have callbacks so you can pass in a function to get invoked by the directive. However, when that callback is invoked by the directive, it doesn't seem like the changes to $scope attributes are fully updated like I would expect them to be.
Here is a Plunker that shows this behavior:
http://embed.plnkr.co/Rg15FHtHgCDExxOYNwNa/
Here is what the code looks like:
<script>
var app = angular.module('myApp', []);
app.controller('Controller', ['$scope',function($scope) {
// initialize the value to something obvious
$scope.clickersValue = "BEFORE";
// when this call back is called we would expect the value to be updated by updated by the directive
$scope.clickersCallback = function() {
//$scope.$apply(); // $apply is not allowed here
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};
}
]);
app.directive('clicker', [function() {
return {
restrict: 'EA',
template: '<div ng-click="clicked()">click me!</div>',
controller: ['$scope', function($scope) {
$scope.clicked = function() {
console.log("you clicked me.");
$scope.newValue = 'VALID';
$scope.myUpdate();
}
}],
scope: {
"newValue": "=",
"myUpdate": "&"
}
};
}]);
</script>
So when clickersCallback gets invoked the clickersValue attribute still has the old value. I have tried using $scope.$apply but of course it isn't allowed when another update is happening. I also tried using controller_bind but got the same effect.
Wrap the code inside clickersCallback function in a $timeout function.
$timeout(function() {
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
});
Updated plunker
The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
source
Edit 1: As the OP said below, the user of the directive should not have to write any "special" code in his callback function.
To achieve this behavior I changed the $timeout from de controller to the directive.
Controller callback function (without changes):
$scope.clickersCallback = function() {
$scope.clickersValueRightAfterCall = $scope.clickersValue;
console.log("clickersCallback: scope.clickersValue", $scope.clickersValue);
};
Directive code (inject $timeout in the directive):
$scope.clicked = function() {
console.log("you clicked me.");
$scope.newValue = 'VALID';
$timeout(function() {
$scope.myUpdate();
});
}
Updated plunker
Instead of going into all of the views in an AngularJS application, is there an easier way to add the spellcheck="false" attribute dynamically to all forms in an AngularJS application?
Once added, every form in the application would be like this:
<form spellcheck="false">
Angular actually overrides the HTML FORM element as a directive.
https://github.com/angular/angular.js/blob/master/src/ng/directive/form.js
You can decorate this directive in your app configuration like so:
angular.module('app', [])
.config(['$provide', function ($provide) {
$provide.decorator('formDirective', ['$delegate', function ($delegate) {
var formDirective = $delegate[0];
var oldCompile = formDirective.compile;
formDirective.compile = function (tElement, tAttrs, transclude) {
// get original links
var compile = oldCompile ? oldCompile.apply(this, arguments) : {};
tElement.attr("spellcheck", "false");
return compile;
};
return $delegate;
}]);
}]);
Why don't you just add it with jQuery after the DOM loads?
$(document).ready(function () {
// ... cool onLoad stuff
// Set form spellcheck attributes to false
$('form').attr('spellcheck', false);
});
setAttribute supported by all mainstream browsers.
So the simple way is just as follows(no jQuery needed):
angular.element(document).ready(() => {
// type of attribute value should be String
document.body.setAttribute('spellcheck', 'false');
});
the most simple answer is to add the attribute spellcheck="false" directly to your body element(WHY NOT?).
We are using bootstrap datepicker for our project needs. What we need to do is that whenever user selects the today's date, date has to be shown appended with "(TODAY)" like "May 12, 2008 (TODAY)" in the textbox.
What can be the best approach here? As we are using this datepicker at multiple places, I think having a general approach like creating a directive would be helpful. Was trying to bind change event with the datepickerPopup directive, but have not been able to make it work.
Here is what I have tried so far:
Have created one decorator. This seems to be working. However one issue, how do I access the parent directive methods in this decorator (such as dateFilter, parseDate here)? (sorry if you find my questions naïve, as I am very new to angularjs). I have attached the code.
app.config(function($provide) {
$provide.decorator('datepickerPopupDirective', function($delegate) {
var directive = $delegate[0],
link = directive.link;
//closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
//angular.extend(directive.scope, { 'monthChanged': '&' });
directive.compile = function() {
return function(scope, element, attrs, ngModel) {
link.apply(this, arguments);
ngModel.$render = function() {
var date = ngModel.$viewValue ? dateFilter.apply(ngModel.$viewValue, dateFormat) : '';
var currentDate = new Date();
currentDate.setHours(0,0,0,0);
if(date.getTime() == currentDate.getTime()){
element.val(date + "(TODAY)");
}else{
ngModel.$setViewValue(scope.date);
}
scope.date = parseDate( ngModel.$modelValue );
};
};
};
return $delegate;
});
});
You've got a couple of options. The quick and dirty way would be to extract the component from the third party library into your own directive and template, and modify as needed. The disadvantage to this is that you'll no longer be up to date with the component. Future versions would require you to manually update your directive, which you might not care about... yet.
The second option, is to take advantage of angular's $provide.decorator
This post gives you an initial idea
What are "decorators" and how are they used?
Here's a basic example of decorating a directive definition object
app.directive("foo", function() {
return {
replace: true,
template: '<div>This is foo directive</div>'
};
});
app.config(function($provide) {
$provide.decorator('fooDirective', function($delegate) {
var directive = $delegate[0];
directive.restrict = "AM";
return $delegate;
});
});
In your case, you'll want to override what value is being referenced on the template. You could decorate the entire directive if you want to completely modify it.
I would recommend this as the best approach when looking to tackle modification of a third party library.
Here's an example of decorating a directive to override a scope level function, and use an existing scope variable within the directive while overriding it.
https://jsfiddle.net/wvty8rpc/3/
I have page:
<div>111</div><div id="123" ng-init='foo=555'>{{foo}}</div>
in browser:
111
555
Code js refresh id=123 and get new html. I get:
<div id="123" ng-init='foo="444new"'><span>..</span><b>NEW TEXT<b> {{foo}}</div>
in browser
111
...NEW TEXT {{foo}}
I want get in browser:
111
...NEW TEXT 444new
Is it possible to re-run the initialization angular in this situation?
DEMO: jsfiddle.net/UwLQR
Solution for me: http://jsbin.com/iSUBOqa/8/edit - this BAD PRACTICE!
UPD two months later: My God, what nonsense I wrote. :(
See my notes in the included code and the live demo here (click).
The two reasons that angular will not register data-binding or directives are that the element isn't compiled, or the change happens outside of Angular. Using the $compile service, the compile function in directives, and $scope.$apply are the solutions. See below for usage.
Sample markup:
<div my-directive></div>
<div my-directive2></div>
<button id="bad-button">Bad Button!</button>
Sample code:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.foo = '123!';
$scope.bar = 'abc!';
//this is bad practice! just to demonstrate!
var badButton = document.getElementById('bad-button');
badButton.addEventListener('click', function() {
//in here, the context is outside of angular, so use $apply to tell Angular about changes!
$scope.$apply($scope.foo = "Foo is changed!");
});
});
app.directive('myDirective', function($compile) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
//when using a link function, you must use $compile on the element
var newElem = angular.element('<div>{{foo}}</div>');
element.append(newElem);
$compile(newElem)(scope);
//or you can use:
//$compile(element.contents())(scope);
}
};
});
app.directive('myDirective2', function($compile) {
return {
restrict: 'A',
compile: function(element, attrs) {
//compile functions don't have access to scope, but they automatically compile the element
var newElem = angular.element('<div>{{bar}}</div>');
element.append(newElem);
}
};
});
Update based on your comment
It makes me cringe to write this, but this is what you would need to make that code work.
var elem = document.getElementById('123');
elem.innerHTML = "<div ng-init=\"foo='qwe123'\">{{foo}}</div>";
$scope.$apply($compile(elem)($scope));
Just as I said, you need to compile the element AND, since that is in an event listener, you need to use $apply as well, so that Angular will know about the compile you're doing.
That said, if you're doing anything like this at all, you REALLY need to learn more about angular. Anything like that should be done via directives and NEVER with any direct DOM manipulation.
Try next:
$scope.$apply(function() {
// your js updates here..
});
or
$compile('your html here')(scope);
Look $compile example at bottom of page.
I created a directive using angularJS v1.0.0 and everything works fine, now I updated angular to v1.0.7 and my directive is not working any more, I tried many different ways to fix it but I couldn't make it work.
I tried to replace $beginRouteChange for $routeChangeStart and $afterRouteChange for $routeChangeSuccess and still not work
It is simply a text showing a "loading..." massage while the application is busy. You can see the explample in here:
http://mhevery.github.io/angular-phonecat/app/#/phones
Directive:
working version in AngularJS 1.0.0 but not in v1.0.7
'use strict';
/* Directives */
var directive = {};
directive.butterBar = function($rootScope) {
return {
restrict: 'C',
link: function(scope, element) {
$rootScope.$on('$beginRouteChange', function() {
element.addClass('show');
element.text('Loading...');
});
$rootScope.$on('$afterRouteChange', function() {
element.removeClass('show');
element.text('');
});
}
};
};
angular.module('phonecatDirectives', []).directive(directive);
Thanks!
The correct events for catching route change are:
$routeChangeStart
$routeChangeSuccess
$routeChangeError
$routeUpdate
See $route Documentation
Usage in your case:
$rootScope.$on('$routeChangeStart', function() {
element.addClass('show');
element.text('Loading...');
});
$rootScope.$on('$routeChangeSuccess', function() {
element.removeClass('show');
element.text('');
});
Please check the docs: http://docs.angularjs.org/api/ng.$route
$beginRouteChange and $afterRouteChange are not supported.
Instead use $routeChangeStart and $routeChangeSuccess.