Fail to catch event when call directive method from controller in Angular - angularjs

I try to call directive method from controller by using $broadcast.
But I catch event only if press on button twice.
See Demo in Plunker
Do I miss something?
Here is snippets of code:
HTML
<div ng-controller="MainCtrl">
<test-dir name="{{test}}"></test-dir>
<select ng-model="broadcastTo" ng-options="x as x for x in ['test1', 'test2', 'test3']"></select>
<button ng-click="broadcastToSelectedChild()">test</button>
</div>
JS
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
// a method that broadcasts to a selected child.
$scope.broadcastToSelectedChild = function (){
$scope.test = $scope.broadcastTo;
console.log('call-' + $scope.test);
$scope.$broadcast('call-' + $scope.test);
};
});
app.directive('testDir', function (){
return {
restrict: 'E',
scope: {
'name': '#'
},
template: '<div>{{name}} called: {{called}}</div>',
link: function(scope, elem, attr) {
scope.called = false;
//set up the name to be used as the listened to event.
var eventOn;
scope.$watch('name', function(v) {
console.log('listen ..','call-' + scope.name);
if(eventOn){
eventOn();
}
eventOn = scope.$on('call-' + scope.name, function (){
alert(scope.name);
});
});
}
};
});
Took example from: HERE
Thanks,

It does not work because you bind your directive scope's name to your controller scope's test, but ng-model on the <select> binds to broadcastTo. When you select a value from the <select>, test is not updated and $watch inside your directive does not fire to attach the event handler.
Try:
<test-dir name="{{broadcastTo}}"></test-dir>
DEMO
In your code, you have to click twice because the first click updates test and causes $watch to fire to attach the event handler:
$scope.test = $scope.broadcastTo;
And the second click will be able to handle the event broadcast from your controller.

Related

Accessing scope from within element.bind, within link function of directive

I am experiencing some problems within the link function of my directive. I am starting a new timeout within mousedown event bound to the element, then clearing it on the mouseup. The timeout is not clearing and also other variables I call on the scope are not updating within the element.bind functions. When I log to console, both functions are being triggered but the $scope doesn't seem to update until after the timeout has completed?
How can I make this work? JS fiddle here: http://jsfiddle.net/xrh6dhf9/
HTML
<div ng-app="dr" ng-controller="testCtrl">
<holddelete param="myDeletedMessage" update-fn="doCallback(msg)"></test>
JavaScript
var app = angular.module('dr', []);
app.controller("testCtrl", function($scope) {
$scope.myDeletedMessage = "Deleted Succesfully";
$scope.doCallback = function(msg) {
alert(msg);
}
});
app.directive('holddelete', ['$timeout', function( $timeout) {
return {
restrict: 'E',
scope: {
param: '=',
updateFn: '&'
},
template: "<a href> <i class='fa fa-times fa-fw'></i>Delete {{message}}</a>",
replace: true,
link: function($scope, element, attrs) {
$scope.mytimeout = null;
$scope.message = ">";
element.bind('mousedown', function (e) {
console.log("mousedown");
$scope.message = "- Hold 2 Secs";
$scope.mytimeout = $timeout(function(){
$scope.updateFn({msg: $scope.param});
}, 2000)
});
element.bind('mouseup', function (e) {
console.log("mouseup");
$scope.mytimeout = null;
$scope.message = ">";
})
}
}
}]);
Instead of setting timeout as null use
$timeout.cancel($scope.mytimeout);
Also instead of setting event handlers using element.bind pass execute methods in scope with ng-mousedown and ng-mouseup
Fiddle: http://jsfiddle.net/xrh6dhf9/1/

Angular how to change controller scope from onclick directive

Here is my directive
.directive('closeMapMessage', function($log) {
'use strict';
return function(scope, element) {
var clickingCallback = function() {
angular.element('.map').fadeOut("slow");
};
element.bind('click', clickingCallback);
};
})
How can I change a scope variable in the controller ?
<div class="msg-mobile" ng-show="showInstructionModal">
<div class="close-map-msg ok-got-it-footer" close-map-message>Ok, got it. </div>
</div>
I basically want to set my showInstructionModalfalse when my close directive is called.
From the current snippet of code, it's hard to tell why you're not using a modal solution tailored for Angular, i.e. AngularUI's modal.
However, in your current code, you're attaching a click event to the element outside of Angular's awareness. That's why clicking on the element will not have effect until the next $digest cycle has run. Also, in Agular you normally don't use directives the way you're trying to do. I would suggest updating the directive to also provide the HTML and then use the ng-clickattribute to attach the event handler via Angular.
Update your directive's code to:
.directive('closeMapMessage', function($log) {
'use strict';
return {
restrict: "AE",
link: function(scope, element) {
scope.closeModal = function() {
angular.element('.map').fadeOut("slow");
scope.showInstructionModal = false; // probably need to put this in a $timeout for example to show the fading of the element
};
},
template: '<div class="close-map-msg ok-got-it-footer" ng-click="closeModal()">Ok, got it.</div>'
};
})
And then update your HTML accordingly:
<div class="msg-mobile" ng-show="showInstructionModal">
<close-map-message></close-map-message>
</div>
You should run digest cycle manually after click event occurrence to update all scope bindings
.directive('closeMapMessage', function($log) {
'use strict';
return function(scope, element) {
var clickingCallback = function() {
angular.element('.map').fadeOut("slow");
scope.$apply();
};
element.bind('click', clickingCallback);
};
})

changing a controller scope variable in a directive is not reflected in controller function

In my directive, I have a controller variable, page which gets incremented when you press the button in the directive. However, the next line, scope.alertPage() which calls the controller function does not reflect this change. Notice, when you click the button page is still alerted as 1!
I know I can fix this by adding $scope.$apply in the controller but then I get the error that says a digest is already taking place.
Plunker
app = angular.module('app', []);
app.controller('myCtrl', function($scope) {
$scope.page = 1;
$scope.alertPage = function() {
alert($scope.page);
}
})
app.directive('incrementer', function() {
return {
scope: {
page: '=',
alertPage: '&'
},
template: '<button ng-click="incrementPage()">increment page</button>',
link: function(scope, elem, attrs) {
scope.incrementPage = function() {
scope.page += 1;
scope.alertPage();
}
}
}
})
html template:
<body ng-app='app' ng-controller='myCtrl'>
page is {{page}}
<incrementer page='page' alert-page='alertPage()'></incrementer>
</body>
The reason why it does not show the updated value immediately is because the 2 way binding updates the parent (or the consumer scope of the directive) scope's bound value only during the digest cycle. Digest cycle happens after the ng-click is triggered. And hence $scope.page in the controller is not yet updated. You can get around this in many ways by using a timeout which will defer the action to run at the end of the digest cycle. You could also do it by setting an object which holds the value as 2-way bound property. Since 2-way bound property and parent scope share the same object reference you will see the change immediately.
Method 1 - using a timeout:
scope.incrementPage = function() {
scope.page += 1;
$timeout(scope.alertPage)
}
Method 2 - Bind an object:
//In your controller
$scope.page2 = {value:1};
//In your directive
scope.incrementPage = function() {
scope.page.value += 1;
scope.alertPage();
}
Method3 - Pass the value using function binding with argument:
//In your controller
$scope.alertPage = function(val) {
alert(val);
}
and
<!--In the view-->
<div incrementer page="page" alert-page="alertPage(page)"></div>
and
//In the directive
scope.incrementPage = function() {
scope.page += 1;
scope.alertPage({page:scope.page});
}
app = angular.module('app', []);
app.controller('myCtrl', function($scope) {
$scope.page = 1;
$scope.page2 = {value:1};
$scope.alertPage = function() {
alert($scope.page);
}
$scope.alertPage2 = function() {
alert($scope.page2.value);
}
})
app.directive('incrementer', function($timeout) {
return {
scope: {
page: '=',
alertPage: '&',
page2:"=",
alertPage2: '&'
},
template: '<button ng-click="incrementPage()">increment page</button>',
link: function(scope, elem, attrs) {
scope.incrementPage = function() {
scope.page += 1;
scope.page2.value += 1;
$timeout(function(){ scope.alertPage() });
scope.alertPage2();
}
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
<div ng-app="app" ng-controller="myCtrl">
<div incrementer page="page" alert-page="alertPage()" page2="page2" alert-page2="alertPage2()"></div>
</div>
You can pass the variable by reference, then the update will be immediate (because you wont copy it, but simply pass its location in memory).
View:
<incrementer page="data" alert-page='alertPage()'></incrementer>
Directive:
link: function(scope, elem, attrs) {
scope.incrementPage = function() {
scope.page.page += 1;
scope.alertPage();
}
The problem is that the timeline is off.
button clicked, incrementPage()
directive scope value incremented (now 2)
alertPage() parent scope value read (still 1)
parent scope updated as part of digest (now 2)
To get around this, you need to either call the alert function after the digest cycle (e.g. $timeout) or you need to watch for changes in the parent scope.
// in controller
$scope.$watch('page', function (currentValue, previousValue) {
// initially triggered with same value
if (currentValue > previousValue) {
alert(currentValue)
}
})
Then change the value naturally.
// in directive html
<button ng-click="page = page + 1">
Try doing this slightly differently. Pass in a function to do the increment rather than incrementing inside the directive
HTML
<incrementer page='incPage()' alert-page='alertPage()'></incrementer>
Controller
$scope.incPage = function() { // Function to increment
$scope.page++;
};
In Directive
scope: {
page: '&', // Receive like this
alertPage: '&'
},
link: function(scope, elem, attrs) {
scope.incrementPage = function() {
scope.page(); // Call as function
scope.alertPage();
}
}

How $watch controller method in directive

I have CKEDITOR, a controller and a directive. This is the method of the controller which should add ng-show and remove ng-hide from the <span>:
$scope.deleteEditorAndSave = () ->
angular.forEach CKEDITOR.instances, (editor) ->
id = editor.element.getAttribute('data_id')
text = editor.getData()
field = editor.element.getNameAtt()
html_field = $(editor.element.$)
html_field.val(text)
showing = editor.element.getAttribute('ng-show')
console.log showing
$timeout( ->
html_field.trigger('input')
$scope.save_field(text, id, field, 'no_call')
editor.destroy()
angular.forEach allClaims(), (claim) ->
console.log "CLAIM", claim
claim[showing.split('.')[1]] = false
)
And I want to call this method from the directive. When I try to do this, the <span> element doesn't re-render. Does anyone know how this can be solved?
Thanks in advance.
I think the recommended practice is to resolve DOM in the directives, once thing that can help you is a directive with isolated scope, this will allow you to use & in your directives scope like this:
<body ng-controller="MainCtrl">
<div user-name="" callme="enableEditor()"></div>
<div>
add
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$scope.enableEditor = function() {
alert("123");
};
}]);
myApp.directive("userName", function() {
return {
restrict: "A",
scope: {
value: "=userName",
callme:"&"
},
template: '<div class="click-to-edit">' +
'Edit' +
'</div>'
};
});
The attribute callme="enableEditor()" is used to pass the method to the scope directive, the directive scope uses & to indicate it is method callme:"&". Another example:
method2="someMethod()" like
scope: {
value: "=userName",
callme:"&",
method2:"&"
},template: '<div class="click-to-edit">' + 'Edit' + 'Save' + '</div>'
This is the recommended way to communicate directives with controllers.

Angular - function inside directive scope gets called when it shouldn't

Hi I am struggling with the following:
http://jsfiddle.net/uqZrB/9/
HTML
<div ng-controller="MyController">
<p>Button Clicked {{ClickCount}} Times </p>
<my-clicker on-click="ButtonClicked($event)">
</my-clicker>
</div>
JS
var MyApp = angular.module('MyApp',[]);
MyApp.directive('myClicker', function() {
return {
restrict: 'E',
scope: {
onClick: "="
},
link: function($scope, element, attrs) {
var button = angular.element("<button>Click Me</button>");
button.bind("mousedown", $scope.onClick);
element.append(button);
}
};
});
MyApp.controller("MyController", function ($scope) {
$scope.ButtonClicked = function($event) {
$scope.ClickCount++;
};
$scope.ClickCount = 0;
});
(using angular1.2 rc : https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.js)
The custom directive "myClicker" should insert a button into the tag, and bind its mousedown event to a function supplied in the directive scope...
I.e. i can pass the a function from the controller, to execute when the button is clicked.
As you can see, when you run the fiddle, the bound event gets run 11 times, on load.... i.e. before the button has event been clicked.
Running its 11 times causes the "10 $digest() iterations reached. Aborting!" error.
Then, when I click the button I get "Cannot call method 'call' of undefined", as if the method was not declared in the scope.
Why does angular try and run the method on loading?
Why is the "onClick" method not available in the scope?
I think I am misunderstanding something about the directive's isolated scope.
Thanks in advance!
The onClick: "=" in your scope definition expects a two-way data-binding, use onClick: "&" to bind executable expressions into an isolate scope. http://docs.angularjs.org/guide/directive
please changes your code in controller as below
var MyApp = angular.module('MyApp',[]);
MyApp.directive('myClicker', function() {
return {
restrict: 'E',
scope: {
onClick: "&"
},
link: function($scope, element, attrs) {
var button = angular.element("<button>Click Me</button>");
button.bind("mousedown", $scope.onClick);
element.append(button);
}
};
});
MyApp.controller("MyController", function ($scope) {
$scope.ButtonClicked = function($event) {
$scope.ClickCount++;
};
$scope.ClickCount = 0;
});

Resources