$scope.$apply() not calling function - javascript

I have a need for a custom click directive, which executes the passed code using scope.$apply().
$(elem).on('click', function(){
scope.$apply(attrs.wdClick);
});
This works fine if I pass something like wd-click="something = !something". But when I try to call a $rootScope function it does not work, however it does work when using the default ng-click.
wd-click="$root.someFunction()" //this does not call the function but ng-click does
I have tried updating the directive to make it work
$(elem).on('click', function(){
$rootScope.$apply(attrs.wdClick);
});
But this does not work either. Any ideas?

attrs.wdClick is a string, so passing it to $apply won't do anything. To call the function you can pass the string to $eval
scope.$apply(function() {
scope.$eval(attrs.wdClick)
});

You should wrap your code in function(){}
scope.$apply(function(){
attrs.wdClick() // this is some sunction I suppose
});

Would you want to call your rootscope method in an another controller? If I understand correctly, try to use this way :
angular.module('app', [])
.controller('Ctrl', function Ctrl1($scope, $rootScope) {
$rootScope.blah = 'Hello';
$scope.yah = 'World'
})
.directive('myTemplate', function() {
return {
restrict: 'E',
templateUrl: 'my-template.html',
scope: {},
controller: ["$scope", "$rootScope", function($scope, $rootScope) {
console.log($rootScope.blah);
console.log($scope.yah);
$scope.test = function(arg) {
console.log(arg);
}
}]
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="Ctrl">
<my-template></my-template>
</div>
<!-- my-template.html -->
<script type="text/ng-template" id="my-template.html">
<label ng-click="test($root.blah)">Click</label>
</script>
</div>
Also you can try on jsfiddle,
http://jsfiddle.net/mg74b/24/

Is there a reason you are trying to use attrs instead of a scope property? Also, you should use $timeout instead of $apply.
angular
.module('app', [])
.directive('myDirective', myDirective);
myDirective.$inject = ['$timeout'];
function myDirective($timeout) {
return {
restrict: 'E',
templateUrl: 'my-template.html',
scope: {
wdClick: '='
},
link: linkFn
};
function linkFn(scope, element) {
element.on('click', function () {
$timeout(scope.wdClick);
});
}
}

Related

Why do I need $rootScope.$apply when inside $rootScope.$on?

Why is $rootScope.$apply() needed in this example to update elements using ng-hide on the page?
In my experience whenever I put $scope.$apply() inside a $scope.$watch I get the "digest already in progress" error. Is this different?
app.component('myComponent', {
controller: function(){
$scope.visible = false;
$rootScope.$on('someEvent', function(){
$scope.visible = true;
$rootScope.$apply(); // why?
});
}
});
Callback registered with $rootScope.$on is triggered by either $rootScope.$broadcast or $rootScope.$emit. If you explore these methods source code you will see that these methods by itself do not trigger $digest cycle (dirty-checking). That means, that $digest should be triggered either by the code that calls $broadcast or $emit, or inside a callback registered with $rootScope.$on.
Usually, it's better to assume that callback is triggered inside $digest loop and it means that callback call should be wrapped with $apply, as in:
$rootScope.$apply($rootScope.$broadcast('event', data));
This is consistent with what angular best practices suggest:
$scope.$apply() should occur as close to the async event binding as
possible.
I can imagine that $scope.$apply is necessary if you want to support events from outside AngularJS world. Take a look and consider differences how to support events from AngularJS (ng-click) and jQuery (onClick).
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.click = function() {
$scope.$emit('inside', {
message: 'from AngularJS world'
})
}
})
.directive('eventListener', function() {
return {
restrict: 'E',
controller: function($scope) {
$scope.message = 'listening'
// in AngularJS context - $apply will throw error that $digest is already running!
$scope.$on('inside', function(event, args) {
$scope.message = args.message
})
// let to know AngularJS that something changes
$scope.$on('outside', function(event, args) {
$scope.$apply(function() {
$scope.message = args.message
}) // this is eqvivalent to:
/*
$scope.message = args.message
$scope.$apply()
*/
})
},
template: '<div>message: {{ message }}</div>'
}
})
.directive('jQueryClick', function() {
return {
restrict: 'E',
link: function(scope, element) {
$(element).on('click', function() {
scope.$emit('outside', {
message: 'outside AngularJS'
})
})
},
template: '<div><button click="click()">jQuery - onClick()!</button></div>',
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl">
<button ng-click="click()">AngularJS ng-click!</button>
</div>
<div>
<j-query-click></j-query-click>
</div>
<div>
<event-listener></event-listener>
</div>
</div>
As this will be hard to decide what is the source of change and how it should be applied to the $scope we can move $apply from $on to $emit part.
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.click = function() {
$scope.$emit('message', {
message: 'from AngularJS world'
})
}
})
.directive('eventListener', function() {
return {
restrict: 'E',
controller: function($scope) {
$scope.message = 'listening'
/*
* We are goint to support internal and external message in the same way,
* we can simplify support for it
*
$scope.$on('inside', function(event, args) {
$scope.message = args.message
})
$scope.$on('outside', function(event, args) {
$scope.message = args.message
})
*/
$scope.$on('message', function(event, args) {
$scope.message = args.message
})
},
template: '<div>message: {{ message }}</div>'
}
})
.directive('jQueryClick', function() {
return {
restrict: 'E',
link: function(scope, element) {
$(element).on('click', function() {
scope.$apply(scope.$emit('message', {
message: 'outside AngularJS'
}))
})
},
template: '<div><button click="click()">jQuery - onClick()!</button></div>',
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl">
<button ng-click="click()">AngularJS ng-click!</button>
</div>
<div>
<j-query-click></j-query-click>
</div>
<div>
<event-listener></event-listener>
</div>
</div>

Proper "Angular" way to pass behavior to directive?

When looking for information regarding Angular directives and passing behavior to directives, I get ended up being pointed in the direction of method binding on an isolate scope, i.e.
scope: {
something: '&'
}
The documentation for this functionality is a bit confusing, and I don't think it'll end up doing what I want.
I ended up coming up with this snippet (simplified for brevity), that works by passing a scope function in HomeCtrl, and the directive does it's work and calls the function. (Just incase it matters, the real code passes back a promise from the directive).
angular.module('app', []);
angular.module('app')
.directive('passingFunction',
function() {
var changeFn,
bump = function() {
console.log('bump() called');
internalValue++;
(changeFn || Function.prototype)(internalValue);
},
internalValue = 42;
return {
template: '<button ng-click="bump()">Click me!</button>',
scope: {
onChange: '<'
},
link: function(scope, element, attrs) {
if (angular.isFunction(scope.onChange)) {
changeFn = scope.onChange;
}
scope.bump = bump;
}
};
})
.controller('HomeCtrl',
function($scope) {
$scope.receive = function(value) {
console.log('receive() called');
$scope.receivedData = value;
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.4/angular.min.js"></script>
<div ng-app="app" ng-controller="HomeCtrl">
<passing-function on-change="receive"></passing-function>
<p>Data from directive: {{receivedData}}</p>
</div>
Is this a proper "Angular" way of achieving this? This seems to work.
What you need is to pass the function to the directive. I'll make a very small example.
On controller:
$scope.thisFn = thisFn(data) { console.log(data); };
In html:
<my-directive passed-fn="thisFn()"></my-directive>
On directive:
.directive('myDirective', [
() => {
return {
restrict: 'E',
scope: {
passFn: '&'
},
template: '<div id="myDiv" ng-click="passFn(data)"></div>',
link: (scope) => {
scope.data = "test";
}
}
}]);

Pass parameters from html directive to controller

I would like to pass an argument from an html directive to a controller
Example:
Directive:
angular.module('app')
.directive('helloWorld', function () {
return {
replace: false,
restrict: 'AE',
templateUrl: "./views/templates/helloWorld.html"
}
});
helloWorld.html:
<body ng-app="app" >
<div ng-controller="HelloWorldCtrl">
{{ welcome }}
</div>
hello.html:
<body ng-app="app">
<hello-world/>
</body>
HelloWorldCtrl:
angular.module('app')
.controller('HomeWorldCtrl', function ($scope, ) {
$scope.welcome = "Welcome"
};
})
Can I specify a parameter in hello.html e.g.
<hello-world param="param1"/>
That is being passed into the controller?
So in the HomeWorldCtrl I can check the value of the parameter?
Are there any better alternatives to achieve this?
Thanks,
app.directive('helloWorld', function(){
return {
restrict:'E',
scope: {
param: '#'
},
template:'<div class="param"><h2>{{param}}</h3></div>'
};
});
// you have options to choose from
//= is two-way binding
//# simply reads the value (one-way binding)
//& is used to bind functions
<hello-world="someParam"></hello-world>
// for the scope definition:
scope: {
title: '#helloWorld'
}
The usage in the template will remain the same
<hello-world param="someParam"></hello-world>
If you isn't use isolated scope (scope: {someparam: ""}), then you may use any $scope properties in the directive template without changing anything:
$scope.param = "new param value";
..
return {
..
,template: "<param>{{param}}</param>"
Thanks!
directive
return {
replace: false,
restrict: 'AE',
templateUrl: "./views/templates/PatientSearchEdit.html",
scope: {
param: '#'
}
}
controller
console.log($scope.param);
Logs indeed the value specified.
Thanks you very much!

AngularJS $rootScope.$emit not Firing the Method

I want to fire a directive based on demand using a service. Below is the directive code.
var app = angular.module('MyModule').service('DOMService', ['$rootScope', function ($rootScope) {
this.Manipulate = function () {
$rootScope.$emit('manipulateDOM');
};
}]).directive('domDirective', ['$rootScope', function ($rootScope) {
return {
restrict: 'EA',
replace: 'false',
scope: true,
link: function (scope, elm, attrs) {
$rootScope.$on("manipulateDOM", function () {
alert("Entered......");
// element.find('#DirectiveLable').css('background-color', 'red')
// element.find('#DirectiveLable').css('height', '100')
});
},
};
}]);
My HTML like below
<div id="example">
<div id="grid"></div>
<dom-directive>
<label id="DirectiveLable">Click Here</label>
</dom-directive>
</div>
I am calling the service like below inside my Angular Controller
angular.module('MyModule').controller('CitizenRelationAccordionController', ['$scope', '$q', '$attrs', 'DOMService',
function ($scope, $q, $attrs, DOMService) {
DOMService.Manipulate();
}
]);
Issue is that eventhough DOMService is firing, it's not invoking the directive method where alert has written
I think that you missed the [] in the module declaration: here is a working example from what you wrote.
Hope this could help,
Dario

angular directive to call scope method on click event

I have a grid with button that has k-grid-cancel-changes class. I would like to create a directive that will attach a click event to that button and call method on the page scope
.directive('kGridCancelChanges', function () {
return {
restrict: 'C',
scope: {
onCancelChanges: "&"
},
controller: function ($scope, $element, $attrs, $location) {
$element.click(function () {
$scope.onCancelChanges();
});
}
}
});
When I press button I can see $scope.onCancelChanges() fired from my directive but it never reaches function on the page scope.
$scope.onCancelChanges = function () {
alert('test');
}
I would appreciate any suggestions
If you want to call a function in the scope it has to be provided like this:
<button class="k-grid-cancel-changes" on-cancel-changes="onCancelChanges()">test</button>
Demo: http://plnkr.co/edit/8vQ1wmdriGrDFGZwhqW2?p=preview
If for some reason you can't modify HTML code (say, it's rendered dynamically by Kendo) and can't add attribute, then you can only access the function to call via $parent scope reference:
$scope.$parent.onCancelChanges();
Demo: http://plnkr.co/edit/tpEEZs9VQunKXABud9yN?p=preview
And finally, if it's not principal to have isolated scope for your directive then you can simply call the function as it's the same scope:
.directive('kGridCancelChanges', function() {
return {
restrict: 'C',
controller: function($scope, $element, $attrs, $location) {
$element.click(function() {
$scope.onCancelChanges();
});
}
}
});
Demo: http://plnkr.co/edit/0OmlCJ6SgYU2GQRyBgYj?p=preview
You can create you directive like this:
app.directive('myDir', function () {
return {
restrict: 'C',
scope: {
foo: '&'
},
link: function(scope,elem){
elem.on('click',function(){
scope.foo();
});
}};
});
or use controller function instead of link if you need:
controller: function($scope,$element){
$element.on('click',function(){
$scope.foo();
});
}};
Note that angular's jqLite has no element.click function.
Here is fiddle:
http://jsfiddle.net/cxo77xb4/2/

Categories