I have a angularjs directive which I require ngSubmit in it:
.directive('testDirective', function(){
return {
scope: {},
require: '?^ngSubmit',
....
In my html I have the ng-submit as the parent of directive:
<form name="testForm" ng-submit="printHello()">
<test-directive></test-directive>
<button type="submit">submit button</button>
</form>
I am wondering why I cannot access it and in the link function, the ngSubmitCtrl is always undefined:
link: function(scope, element, attr, ngSubmitCtrl){
// ngSubmitCtrl is always undefind
}
This is plunker with complete code:
https://next.plnkr.co/edit/8duvT6xHcixvbrDz?open=lib%2Fscript.js&deferRun=1
This is because ngSubmit does not instantiate a controller. It is created in bulk with many other ngEventDirectives and only defines a compile property. Take a look at the source code:
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js
forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(eventName) {
var directiveName = directiveNormalize('ng-' + eventName);
ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]);
}];
}
);
function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) {
return {
restrict: 'A',
compile: function($element, attr) {
// NOTE:
// We expose the powerful `$event` object on the scope that provides access to the Window,
// etc. This is OK, because expressions are not sandboxed any more (and the expression
// sandbox was never meant to be a security feature anyway).
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
fn(scope, {$event: event});
};
if (!$rootScope.$$phase) {
scope.$apply(callback);
} else if (forceAsync) {
scope.$evalAsync(callback);
} else {
try {
callback();
} catch (error) {
$exceptionHandler(error);
}
}
});
};
}
};
}
Related
I am trying to open dialogs, which have their own Controllers, opening them through events.
My problem now is, that I am always getting
Cannot read property $emit of undefined`, because for some reason
my $rootScope is undefined.
How can I inject the $rootScope properly?
I am using Angular 1.6.7.
.directive("opendialog", [function($rootScope) {
return {
link: function(scope, element, attributes) {
element.bind("click", function(event) {
var dialogId = $(element).attr("id");
$rootScope.$emit(dialogId, {
command: "open"
});
});
}
}
}]);
Try this
.directive("opendialog", ["$rootScope", function ($rootScope) {
return {
link: function (scope, element, attributes) {
element.bind("click", function (event) {
var dialogId = $(element).attr("id");
$rootScope.$emit(dialogId, {command: "open"});
});
}
}
}]);
This is some directive that i use for detecting enter key
.directive('enterSubmit', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.bind('keydown', function(event) {
var code = event.keyCode || event.which;
if (code === 13) {
if (!event.shiftKey) {
event.preventDefault();
scope.$apply(attrs.enterSubmit);
}
}
});
}
}
})
I have a function that i wanna get $event as a parameter to it.
$scope.test = function(evt){
var el = angular.element(evt.target);
console.log(el[0]);
}
But when i use $event as i could in built-in directives
<textarea enter-submit="test($event)"></textarea>
It says evt is undefined so how to do it?
Change your if to this:
if (!event.shiftKey) {
event.preventDefault();
var enterSubmitFunction = $parse(attrs['enterSubmit']);
enterSubmitFunction(scope, { $event: event });
scope.$apply();
}
You'll need to inject $parse now, and i'm not sure if you need to use scope.$parent in the enterSubmitFunction or just scope. Try both.
What I want do do
I want to access the item from my scope method like in the examples below
What I've tried
Markup
<span ng-repeat="item in items" on-whatever="handleWhatever(item)">
{{item.name}}
</span>
<span ng-repeat="item in items" on-whatever="handleWhatever(item, $event)">
{{item.name}}
</span>
Controller
app.controller('MyController', function($scope){
$scope.items = [{name:'Item 1'}, {name: 'Item 2'}, ... ];
$scope.handleWhatever = function(item, $event){
console.log('This is the item that whatever happened on', item /*This is always undefined...*/);
console.log('This is the $event that triggered the whatever', $event);
};
});
Directive
app.directive('onWhatever', function () {
var linker = function (scope, element, attrs) {
console.log(attrs);
element.bind("whatever", function (event) {
scope.$apply(function (){
/*And here is my confusion...
How do I access the function parameter here?
$event makes sense, but what about item?*/
scope.onWhatever({'$event': evt});
});
evt.preventDefault();
});
};
return {
link: linker,
scope:{
onWhatever: '&'
}
}
});
I know it is possible, because that's how native directives like ng-click work, but I cannot seem to locate the directive definition inside angular source code...
Following code creates a several directives in loop
forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
From angularjs source code
/*
* A collection of directives that allows creation of custom event handlers that are defined as
* angular expressions and are compiled and executed within the current scope.
*/
var ngEventDirectives = {};
// For events that might fire synchronously during DOM manipulation
// we need to execute their event handlers asynchronously using $evalAsync,
// so that they are not executed in an inconsistent state.
var forceAsyncEvents = {
'blur': true,
'focus': true
};
forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(eventName) {
var directiveName = directiveNormalize('ng-' + eventName);
ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
return {
restrict: 'A',
compile: function($element, attr) {
// We expose the powerful $event object on the scope that provides access to the Window,
// etc. that isn't protected by the fast paths in $parse. We explicitly request better
// checks at the cost of speed since event handler expressions are not executed as
// frequently as regular change detection.
var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
return function ngEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
fn(scope, {$event:event});
};
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
scope.$evalAsync(callback);
} else {
scope.$apply(callback);
}
});
};
}
};
}];
}
);
After the help I received from maurycy, I figured out where I was wrong.
Here is the most bare directive that can access the parameters passed to it's passed functions:
app.directive('onWhatever',function($parse, $rootScope) {
return {
// Notice how we DON'T create an isolate scope. Doing so
// will cause our handler to never actually get access
// to the properties we need
restrict: 'A',
compile: function($element, attr) {
// This fn function that is returned by $parse
// is actually the same thing as using
// scope: {onWhatever: '&'}, but it also
// takes all passed arguments into account
var fn = $parse(attr.onWhatever, null, true);
return function (scope, element) {
element.on('whatever', function(event) {
var callback = function() {
fn(scope, {$event:event});
};
scope.$apply(callback);
});
};
}
};
});
Fiddle
HTML:
<div ng-controller="MyCtrl">
<button my-event-directive>Click me</button>
<div>{{secret}}</div>
</div>
JS:
var myApp = angular.module('myApp',[]);
myApp.directive('myEventDirective', function() {
return {
link: function(scope, element) {
element.on('click', function(event){
scope.$emit('myEvent', {secret: 'aaa'});
});
}
}
})
function MyCtrl($scope) {
$scope.secret = 'bbb';
$scope.$on('myEvent', function(event, data){
alert('event received!');
$scope.secret = data.secret;
});
}
After I click the button, the event is received in the controller (alert shows up). However, the {{secret}} binding does not update its value. Why?
My event creation is more sophisticated in real code, of course.
As #Cherinv replied in a comment, when changing a scope attributes outsite the Angular $apply method, you have to call it manually. #runTarm also suggested that the event dispatcher should use the $apply because listeners are freed from remember it then. So:
scope.$emit('myEvent', {secret: 'aaa'});
should be changed to:
scope.$apply(function() {
scope.$emit('myEvent', {secret: 'aaa'});
});
$apply method is described in details in the following article: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
USE $scope.$apply(). NOW the change will be noticed, and the page is updated.
var myApp = angular.module('myApp',[]);
myApp.directive('myEventDirective', function() {
return {
link: function(scope, element) {
element.on('click', function(event){
scope.$emit('myEvent', {secret: 'aaa'});
});
}
}
})
function MyCtrl($scope) {
$scope.secret = 'bbb';
$scope.$on('myEvent', function(event, data){
alert('event received! secret is ' + data.secret);
$scope.$apply(function () {
$scope.secret = data.secret;
});
});
}
You could try changing binding to happen on object.value rather than value. Maybe it's the case when angular can not trace immutable property change.
<div ng-controller="MyCtrl">
<button my-event-directive>Click me</button>
<div>{{data.secret}}</div>
</div>
var myApp = angular.module('myApp',[]);
myApp.directive('myEventDirective', function() {
return {
link: function(scope, element) {
element.on('click', function(event){
scope.$emit('myEvent', {secret: 'aaa'});
});
}
}
})
function MyCtrl($scope) {
$scope.data = {
secret: 'bbb'
};
$scope.$on('myEvent', function(event, data){
alert('event received!');
$scope.data.secret = data.secret;
});
}
This should work.
P.S. since you always see alert being called that means that you don't need to call scope.$apply to invoke scope digest and the value IS assigned, the problem is that angular can not watch on immutable values (probably)
I have a directive that is almost an exact copy of the old uiIf directive from the angular-ui project.
What is happening is that when I add my "restrict" directive, the button is successfully added/removed based upon what the user is authorized to do.
The problem is that the ng-click action no longer works. It doesn't call in to the controller's scope and trigger the function to be called. Does anyone see what might be causing my issue?
See: http://plnkr.co/edit/38UeVCCYkdzxBkxOLe5g?p=preview
<button restrict="'canPerformAction'" ng-click="action()">Action</button>
'use strict';
angular.module('directives.restrict', [])
.directive('restrict', function(_){
return{
transclude: 'element',
prioriry: 1000,
terminal: true,
restrict: 'A',
compile: function(element, attr, transclude) {
var user = { caps: [ 'canPerformAction', 'canDance', 'canWrite' ] };
return function(scope, element, attr) {
var childElement;
var childScope;
scope.$watch(attr.restrict, function(attributes) {
if (childElement) {
childElement.remove();
childElement = undefined;
}
if (childScope) {
childScope.$destroy();
childScope = undefined;
}
if(_.intersection(user.caps, attributes.split(' ')).length > 0) {
childScope = scope.$new();
transclude(childScope, function(clone) {
childElement = clone;
element.after(clone);
});
}
});
};
}
};
});