AngularJS: Directive to Controller communication with scope.apply not working - angularjs

I have a MainController and a nested Directive. I'm looking at this example to see how the communication works between controllers and directive, but mine doesn't seems to work.
Basically, I want to call a main controller scope function from a custom directive (button empty cart). See the plunkr example below.
Plukr: http://plnkr.co/edit/82STLkKxBK6htTnmnqlu?p=preview
Whenever I do console.log(scope.$apply("emptyCart()")), it's undefined for some reason.
Note: I'm trying to avoid $rootScope.broadcast as much as possible...

You're using isolate scope for the parent directive, so the child directive does not have access to the scope of the controller.
In order to provide the child directive with access to that scope function while maintaining isolation of the parent, you can add that function as a scope: { ... } property on the parent directive:
scope: {
...
emptyCart: '='
}
and set the function name to the corresponding attribute on the parent directive's view declaration:
<div ... data-show="showPopup" empty-cart="emptyCart"></div>
Then you can skip all of the workarounds you've attempted to employ in your Plunker, and just set an ng-click on the child directive in order to fire the controller function:
sHTML = "<button ... ng-click='emptyCart()'>Empty cart</button>";
Demo

Related

Access directive scope from parent controller

I am using angularjs ui tour https://github.com/benmarch/angular-ui-tour
I have successfully installed the directive,
I now want to initialize the directive in my controller when the page is loaded.
On my routes, I have the following code
when('/start', {
templateUrl: 'start.html',
controller: 'startController'
})
On the template start.html, I have the following code
<div ui-tour class="myClass">
So I want to access the tour var of ui-tour scope from startController, how can I do that?
That depends on how directive is defined. Does it create a new scope or not? Is it's scope proto inherited or isolated one. The simplest solution is to make your directive not create a new scope but use parent's one. If it is the same scope, simply call a function in the directive from parents controller like you would do for a function in that controller.
For more info about why is not a straightforward answer and what are the options when defining directives concerning scope, I find this well explained on the following link:
directives and scopes
Taking a look at the documentation, it looks like it's possible. Via the uiTourService documentation
yourModule.controller('startController',
['$scope', 'uiTourService', function($scope, uiTourService) {
var myTour = uiTourService.getTour();
}]);

Replace parent scope with directive attribute controller scope

I've had no success creating an Attribute directive that uses vm in the child elements and uses the directive controller scope and no it's parent scope.
See: http://fiddle.jshell.net/vzuf9psq/
How can I make the second message show the message from the directive controller?
Use directive scope to transfer properties from one controller to another
Use bindToController to bind directive scope to controller (or much better, use Angular component's syntax).
Do not paste template inside directive tag (or use transclude for this purpose)
See Final fiddle

AngularJS - Access a parent controller from child -> child directive with the controllerAs notation

I'd like to access a function from the parent => parent controller inside my directive (with the controllerAs notation). I'm using angular 1.3.14
I have the following structure:
Controller (with save function)
(Child) controller
Directive with a template (and isolated scope). Inside this template I have a button which should call the save function from the parent (parent) controller.
I don't want to call $scope.$parent.$parent.save(...)
Does anyone has an idea?
Thanks in advance.
Use just $scope.save().
In Angular there is scope hierarchy and by calling $scope.save() directive will look in directive's scope, if there is no save() method, it will look in parent's scope and so on.
One condition is to don't have isolated scope for directive.
There is no good way to do this other than passing the desired function into the directive. This is what using & in an isolated scope declaration is for.
https://docs.angularjs.org/guide/directive
Of course there are easier ways if the function is just a utility function. You could just register a filter. But if you want the child directive to alter the state of the parent controller at some event then using & is the best solution.
If you need the child directive to pass arguments to the function that is being passed to it then you are going to need to use the feature associated with this sentence in the above documentation:
This is specified in the directive by calling close({message: 'closing
for now'}). Then the local variable message will be available within
the on-close expression.

Update controller scope from directive

I am creating reusable UI components with AngularJS directives. I would like to have a controller that contains my business logic with the nested components (directives). I want the directives to be able to manipulate a single property on the controller scope. The directives need to have an isolate scope because I might use the same directive more than once, and each instance needs to be bound to a particular controller scope property.
So far, the only way I can apply changes back to the controller's scope is to call scope.$apply() from the directive. But this breaks when I'm inside of an ng-click callback because of rootScope:inprog (scope operation in progress) errors.
So my question: What is the best way to make my controller aware when a child directive has updated a value on the controller's scope?
I've considered having a function on the controller that the directive could call to make an update, but that seems heavy to me.
Here is my code that breaks on an ng-click callback. Keep in mind that I don't just want to solve the ng-click issue. I want the best overall solution to apply reusable directives to modify a parent scope/model.
html
<div ng-controller="myCtrl">
<my-directive value="val1"></my-directive>
</div>
controller
...
.controller('myCtrl', ['$scope', function ($scope) {
$scope.val1 = 'something';
}});
directive
...
.directive('myDirective', [function () {
return {
link: function(scope) {
scope.buttonClick = function () {
var val = 'new value';
scope.value = val;
scope.$apply();
};
},
scope: {
value: '='
},
template: '<button ng-click="buttonClick()"></button>'
};
}]);
The purpose of two-way data binding in directives is exactly what you're asking about -- to "[allow] directives to modify a parent scope/model."
First, double-check that you have set up two-way data binding correctly on the directive attribute which exposes the variable you want to share between scopes. In the controller, you can use $watch to detect updates if you need to do something when the value changes. In addition, you have the option of adding an event-handler attribute to the directive. This allows the directive to call a function when something happens. Here's an example:
<div ng-controller="myCtrl">
<my-directive value="val1" on-val-change="myFunc"> <!-- Added on-change binding -->
<button ng-click="buttonClick()"></button>
</my-directive>
</div>
I think your question about $scope.apply is a red herring. I'm not sure what problem it was solving for you as you evolved this demo and question, but that's not what it's for, and FWIW your example works for me without it.
You're not supposed to have to worry about this issue ("make controller aware ... that [something] modified a value on a scope"); Angular's data binding takes care of that automatically.
It is a little complicated here because with the directive, there are multiple scopes to worry about. The outer scope belongs to the <div ng-controller=myCtrl>, and that scope has a .val property, and there's an inner scope created by the <my-directive> which also has a .val property, and the buttonClick handler inside myDirective modifies the inner one. But you declared myDirective's scope with value: '=' which sets up bidirectional syncing of that property value between the inner and outer scope.
So it should work automatically, and in the plunker I created from your question code, it does work automatically.
So where does scope.$apply come in? It's explicitly for triggering a digest cycle when Angular doesn't know it needs to. (And if you use it when Angular did know it needed a digest cycle already, you get a nested digest cycle and the "inprog" error you noticed.) Here's the doc link, from which I quote "$apply() is used to execute an expression in angular from outside of the angular framework". You need to use it, for example, when responding to an event handler set up with non-Angular methods -- direct DOM event bindings, jQuery, socket.io, etc. If you're using these mechanisms in an Angular app it's often best to wrap them in a directive or service that handles the Angular-to-non-Angular interface so the rest of your app doesn't have to worry about it.
(scope.$apply is actually a wrapper around scope.$digest that also manages exception handling. This isn't very clear from the docs. I find it easier to understand the name/behavior of $digest, and then consider $apply to be "the friendlier version of $digest that I'm actually supposed to use".)
One final note on $apply; it takes a function callback argument and you're supposed to do the work inside this callback. If you do some work and then call $apply with no arguments afterwards, it works, but at that point it's the same as $digest. So if you did need to use $apply here, it should look more like:
scope.buttonClick = function() {
scope.$apply(function() {
scope.value = newValue;
});
});

Can we link two controllers to a single custom directive

I tried to find linking of multiple controllers to a single custom directive, but no solution. Is this possible to achieve or not. Can anybody please tell me.
Requirement: I created a directive with a controller. I'm calling that directive in a page and the page is having its own controller. Now the page controller have a couple of functions. I'm using a template with some events. Those events are implemented in the page controller (parent controller). So those functions are not firing.
<div ng-controller="controllername">
<myDirective name-"name" event="doSomeEvent(params)"/>
In the controller i have a couple of functions like
app.controller("controllername",['$scope','function($scope))
{
$scope.functionName = function()
{
alert(1);
}]
}
This function is linked to the directive template. How to make this event fired?
my guess is that your directive has got an isolated scope.
meaning you have in your directive definition a line with scope: {}.
that makes it isolated and it can't see the parent scope (meaning that controller 'controllername' you have there)
remove the scope definition from the directive (remove the scope: {}) and you will have access to the parent scope.
and you will be able to use those function as if they were in the directive scope.

Resources