Directive trigger controller before model is updated - angularjs

I created a custom directive that create two buttons. Each button use ng-click to call a method inside the directive to change a value of the model and then call the controller to update the value in the database. In this way the controller is called before the model is updated because it always use the old value.
If I apply inside the directive $scope.$apply() everything works fine, except an error is fired in the consolle because there are an $apply() executing yet. How properly wait the model update before to call the controller?
The directive is used into an ng-repeat that iterate an array.
Logic I used is:
User Click one of the Buttons of the switch (On or Off i.e.)
Button Trigger directive setModel(value) method
setModel(value) set value to the model and call controller passing index of the object
controller get the object by index and update in database
If i try to send the value edited to the controller, of course, it works, but i would play with models and not with each values. Why the model is updated before controller do it's $http calls?
This is the directive:
app.directive('ngSwitch', function () {
return {
scope: {
model: '=ngSwitch',
index: '=index', //Index in the array
switchValue1: '#switchValue1',
switchDesc1: '#switchDesc1',
switchValue2: '#switchValue2',
switchDesc2: '#switchDesc2',
callback: '=callback'
},
link: function (scope, elm, attr) {
scope.saving = false;
scope.setModel = function (value) {
scope.model = value == 1 ? scope.switchValue1 : scope.switchValue2;
scope.saving = true;
scope.$apply();//IF I DON'T USE $apply, IT DOESN'T WORK
scope.callback(scope.index)
}
},
templateUrl: 'components/direttive/ngSwitch.html'
};
});

Related

Angularjs directive two-way bound variable changes are not triggering $digest on the parent scope

Apologies if some of the JS is syntactically off. I wrote it while looking at my CoffeeScript
I have a text editor that I've extracted into a directive and I want to share some state between it and its containing template:
Main containing template
<div class="content">
<editor class="editor" ng-model="foo.bar.content" text-model="foo.bar"></editor>
</div>
Template Controller
angular.module('foo').controller('fooController', ['$scope', ... , function ($scope, ...) {
$scope.foo = {}
$scope.foo.bar = {}
$scope.foo.bar.content = 'starting content'
$scope.$watch('foo.bar', function () {
console.log('content changed')
}, true)
}
The template two-way binds on its scope object $scope.foo.bar with the editor directive. When the text is changed, the editor's 'text-change' handler is fired and a property on the bound object is changed.
Editor Directive
angular.module('foo').directive('editor'), function (
restrict: 'E',
templateUrl: 'path/to/editor.html',
require: 'ng-model',
scope: {
textModel: '='
},
controller: [
...
$scope.editor = 'something that manages the text'
...
],
link: function (scope, ...) {
scope.editor.on('text-change', function () {
$scope.textModel.content = scope.editor.getText()
// forces parent to update. It only triggers the $watch once without this
// scope.$parent.$apply()
}
}
However, changing this property in the directive seems not to be hitting the deep $watch I've set on foo.bar. After some digging, I was able to use the directive's parent reference to force a $digest cycle scope.$parent.$apply(). I really shouldn't need to though, since the property is shared and should trigger automatically. Why does it not trigger automatically?
Here are some good readings that I've encountered that are pertinent:
$watch an object
https://www.sitepoint.com/understanding-angulars-apply-digest/
The on function is a jqLite/jQuery function. It will not trigger digest cycle. It is basically outside the angular's conscious. You need to manually trigger digest cycle using $apply.

angularjs create custom event directive

I have a simple attribute-restricted directive like
app.directive('akMouseOver', function () {
return {
restrict: 'A',
scope: {
mouseOver: '&akMouseOver'
},
controller: function ($scope) {
},
link: function (scope, elem, attrs) {
elem.bind('mouseover', function () {
scope.mouseOver({ hValue: value });
});
}
}})
that I am calling on a simple HTML button as
<button ak-mouse-over='btnMouseOver('Win Win')' class='btn btn-primary'> Hello There !</button>
and my parent controller method is
$scope.btnMouseOver = function (hValue) {
alert(hValue + 'Hello !!!');
}
Here, somehow, I am unable to pass a parameter to the parent method. If I make this implementation without parameter, it is working and I see alert(), if I hover the mouse over the button.
Looking for passing (a) parameter/s without adding additional attribute/directive/scope variable.
In your case it should work & then it would alert with Win Win Hello !!! because you had hardcoded value of function level, even if you pass value from directive it will just pass the same.
While passing value from directive to registered function of isolated scope, you should have btnMouseOver(hValue), because when you are calling mouseOver function of directive which will basically going to call btnMouseOver method registered on ak-mouse-over attribute.
At the time of passing value you need to have pass value to parent controller function in JSON kind of format like {hValue: value} where hValue will represent parameter of btnMouseOver function, placed over a ak-mouse-over and then value is value which you are passing to function.
<button ak-mouse-over="btnMouseOver(hValue)">
Hello There !
</button>
Also you need to call scope.$apply() from mouserover event handler to keep of digest cycle as you are running an event outside angular context.
Demo here

How to include data/scope from controller in a dynamically added directive?

I'm trying to figure out how to include scope with a directive that I add to the dom on a click event in a controller.
Step 1. On a click event, I call a function in my controller that adds a directive like this
$scope.addMyDirective = function(e, instanceOfAnObjectPassedInClickEvent){
$(e.currentTarget).append($compile("<my-directive mydata='instanceOfAnObjectPassedInClickEvent'/>")($scope));
}
//I'm trying to take the `instanceOfAnObjectPassedInClickEvent` and make it available in the directive through `mydata`
The above, part of which I got from this SO answer, successfully adds the directive (and the directive has a template that gets added to the dom), however, inside the directive, I'm not able to access any of the scope data mydata it says it's undefined.
My directive
app.directive('myDirective', function(){
return {
restrict: 'AE',
scope: {
mydata: '='
//also doesn't work if I do mydata: '#'
},
template: '<div class="blah">yippee</div>',
link: function(scope,elem,attrs) {
console.log(scope) //inspecting scope shows that mydata is undefined
}
}
}
Update
I changed the name of datafromclickedscope in the OP to make it more clear. In the controller action addMyDirective (see above) instanceOfAnObjectPassedInClickEvent is an instance of an object passed into the controller method on a click event that I try to pass into the directive as mydata='instanceOfAnObjectPassedInClickEvent'. However, even if I change = to # in the directive and I try to access scope.mydata in the link function of the directive, it just shows a string like this "instanceOfAnObjectPassedInClickEvent", not the actual object data that is available to me in my method that handles the click event
When you use mydata='instanceOfAnObjectPassedInClickEvent' in a template you need instanceOfAnObjectPassedInClickEvent to defined in $scope. So before compiling you should assign a variable in $scope. I will rename this variable in code below, so that same names would not confuse you and it would be clear that a formal parameter of a function cannot be visible in a template.
$scope.addMyDirective = function(e, instanceOfAnObjectPassedInClickEvent){
$scope.myEvent = instanceOfAnObjectPassedInClickEvent;
$(e.currentTarget).append($compile("<my-directive mydata='myEvent'/>")($scope));
}
EDIT: slightly adapted jsfiddle not using JQuery no manipulate DOM

Two-way binding within nested directives and ui-select

Problem I have been working on:
The original problem (which only addressed one-way binding) can be found here:
Unable to pass/update ngModel from controller to directive
I have managed to get the one-way binding from inside the directive out to the view working. But if I want push in a selection when the page loads, for example, I cannot get the two-way binding to function. I can see that the ng-model inside the first directive is getting the data, but I tried various scope settings with the child directive and either it breaks it or it does the same thing as before - nothing.
I have a basic $watch function set up, so that when I push a simple object into the binding that is attached to ng-model in the view, the watcher assigns the $viewValue to the directive's scope object. It does this, and the directive responds only by having any existing selection wiped out, even though I can clearly see the objects inside ng-model binding assigned to ui-select.
Here is the watch function:
scope.$watch(function() {
return ngModel.$viewValue;
}, function(newVal) {
console.log(newVal, scope.options);
scope.options.selected = newVal;
});
I use this function to update the view whenever we interact with the ui-select (which works fine):
scope.changer = function() {
ngModel.$setViewValue(scope.options.selected);
console.log(scope.options.selected);
};
A Plunker for tinkering
So the expected behavior is:
selections from the ui-select should be displayed in the select, and also get passed to the view
by clicking the 'Change' button, data should be displayed in the view, and get passed to the ui-select
The situation was that I had a directive with ui-select inside it. The way I was able to get data from the main controller scope through the directive scope and down to ui-select's scope was by putting a watch function on ngModel.$viewValue in the link function of the directive.
I then assigned that new value, whenever there was a change, to an object on that directive's scope, to hold it for me. Then I set a watch function in the link function of ui-select to watch scope.$parent.myVal so that if anything was ever pushed to that variable from the main controller, ui-select would see it. When that happened, I would assign that new value to $select.selected which is the container for selected items in the ui-select directive.
It should be noted that ui-select uses scope: true in it's directive, and thus becomes a child of whatever scope it is instantiated in, which allows you to access the scope of it's parent with $parent.
Watch function for the directive:
scope.$watch(function() {
return scope.$parent.myVar;
}, function(newVal) {
$select.selected = newVal;
})
Watch function to be added to ui-select:
scope.$watch(function() {
return ngModel.$viewValue;
}, function(newVal) {
scope.myVar = newVal;
})
Plunker Demo

$locationChangeSuccess triggers four times

I am new to angular Js.
My application flow is as below:
1) I have a view controller wherein, each view controller sets the breadcrumb data with the help of Breadcrumbs factory.
2) Breadcrumbs factory takes data from view controller and attaches data to $location.$$state object.(reason for storing in state object is if back button is pressed, view controller doesn't instantiate so I can refer history data for breadcrumbs ) below is code to attach data to state object:
var state = $location.state();
state.breadcrumb = breadcrumbData;
$location.replace().state(state);
3) I have also created breadcrumb directive on global header which will display breadcrumbs on $locationChangeSuccess event. Directive will take data from $location.state(); which was set in factory.
My problem is when location is changed, $locationChangeSuccess event callback function executes four times.
below is my directive code:
angular.module('cw-ui')
.directive('cwBreadcrumbs', function($location, Breadcrumbs, $rootScope) {
return {
restrict: 'E',
replace: true,
templateUrl: 'UI/Directives/breadcrumb',
link: function($scope, element){
//some code for element...
$rootScope.$on('$locationChangeSuccess', function(event, url, oldUrl, state, oldState){
// get data from history of location state
var data = $location.state();
console.log(data);
});
}
};
});
output is as below:
Object {}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0], breadcrumb: Array[2]}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0]}
Object {key: "Core/Views/dash:1", view: "Core/Views/dash", parameters: Array[0]}
breadcrumb: Array[2] disappears 1st, 3rd and 4th times. I really don't know what is causing this callback function execute four times, and I have no clue about an issue and don't know how to debug. Please help guys!
After running into this myself, the problem lies in the fact you are using the root scope to bind the locationChangeSuccess event from within a directive that is either encountered multiple times on a single page, or encountered multiple times as you revisit the page:
$rootScope.$on('$locationChangeSuccess', function(event, url, oldUrl, state, oldState){
Since you are binding to the rootScope, and the rootScope does not go out of scope, the event binding is not cleaned up for you.
Inside your link function, you should add a listener for the element $destroy, as well as capture the return value from the original bind, so you can later unbind it.
First: capture return value:
var unbindChangeSuccess = $rootScope.$on('$locationChangeSuccess' ...
Next, unbind that value in your destroy method:
element.on('$destroy', function() {
unbindChangeSuccess();
});
That should solve the multiple calls to your locationChangeSuccess! :)

Resources