prevent api call from execution repeatedly in directive - angularjs

I am making a api call from a directive. There is a condition to show directive. I am handling it using ng-show. But when first time html loads api call is executed even if ng-show flag is false.
I want to make api call only at first time when ng-show flag is true. After change in this ng-show flag i do not want to make api call.
I have done something like this.
angular.module("module").directive("customDirective", function(){
return {
templateUrl : "cutomTemplate.html",
replace : true,
scope : {},
link : {
apicall();
}
};
});
<custom-directive ng-show="value === 1"></custom-directive>

Here is a solution i came up. This involves registering a watch on ngShow attribute in your directive scope and then de registering it once api call is made. See my fiddle here http://jsfiddle.net/cmyworld/u57dw8ws/
The directive now looks like:
angular.module("module").directive("customDirective", function () {
return {
scope: {
ngShow: '='
},
link: function (scope, element, attr) {
var observeFn = scope.$watch('ngShow', function (value) {
if (value) {
console.log('Making api call');
observeFn(); // degister after first callback,
}
});
}
};
});
ng-if may not work, if you show hide the element again and again.

Related

Why won't parent model get updated on Angular Directive function call?

http://jsfiddle.net/2dgzt18a/
I'm expecting the model on the parent to get updated when Enter is pressed in the input. But it does not. Output from console log looks promising, like it should do it. Do I need to use a $watch ? Doubt it, but thought I'd ask.
HTML
<div data-ng-app="testApp">
<div data-ng-controller="testCtrl">
<strong>{{pkey}}</strong>
<span data-test-directive
data-parent-item="pkey"
data-parent-update="update(pkey)"></span>
</div>
</div>
DIRECTIVE
var testApp = angular.module('testApp', []);
testApp.directive('testDirective', function ($timeout) {
return {
scope: {
key: '=parentItem',
parentUpdate: '&'
},
replace: true,
template: '<div><input type="text"></input></div>',
link: function(scope, elem, attrs) {
elem.bind('keydown keypress', function(event) {
if (event.which === 13) {
scope.parentUpdate({ pkey: 'D+' + scope.key});
event.preventDefault();
}
})
}
};
});
CONTROLLER
testApp.controller('testCtrl', function ($scope) {
$scope.pkey = 'golden';
$scope.update = function (k) {
// Expecting local variable k, or $scope.pkey to have been
// updated by calls in the directive's scope.
console.log('CTRL:', $scope.pkey, k);
$scope.pkey = 'C+' + k;
console.log('CTRL:', $scope.pkey);
};
});
I believe I have seen this work using a controller in a directive, but since I'm interested in a Keypress event, is why I need to use link.
elem.bind just binds js function to event, nothing else.
Add scope.$apply().
P.S. i.e. ng-click does nearly the same: binds event and call apply after callback.
P.S.1. If you can use more modern angular version - there are ng-keypress and similar directives.
scope.$apply is not preferred to use. It is better to use $timeout
The $timeout does not generate error like „$digest already in
progress“ because $timeout tells Angular that after the current cycle,
there is a timeout waiting and this way it ensures that there will not
any collisions between digest cycles and thus output of $timeout will
execute on a new $digest cycle

Directive improvement with watch and conditional templating

Here is the directive:
.directive("unloggedWarning", function () {
return {
restrict: "EA",
link: function (scope) {
scope.$watch('currentUser', function() {
if(scope.currentUser === null) {
scope.notLogged = true;
} else {
scope.notLogged = false;
} });
}
};
})
currentUser is rootscope persistant user current status with Parse backend. So whenever user logs out, the watch will set notLogged to true. I guess I can then in the html file view use conditonal ng-if to display warning when user unlogs.
How could I improve this directive, so from INSIDE the directive, I can conditionally inject a template with some html in it ? I can't seem to pass the log status from the if statement, to a standard directive "template: " parameter inside the directive.
Here is a working example: http://plnkr.co/edit/S9fVZKXLx0Fc6QpkI8iH?p=preview
All I did was add the template:
template: '<div><div ng-show="notLogged">You are not logged in</div></div>',

Angular - detect changed to the same value

I have a controller and a directive. In the directive there is array of data from the controller.
When a button from the directive is clicked I want the controller to update the data.
So the button is clicked, I show a busy indicator and when the controller finishes to update the data I want to hide the busy indicator. My problem is that some times the data stays the same so the "watchCollection" function is not triggered.
Bottom line, I'm looking for a solution that will allow the controller to tell the directive that he has finished the update.
** Another solution I thought of is adding a boolean for "updating". The directive will change it to true and the controller will change it to false when it is finished. But the problem again is that in some cases the value of "updating" is changed back to false before the digest cycle is started detects that is was changed to true.
The directive looks something like this:
Directive:
{
scope: {
arrayData: "=",
onChangeData: "&"
},
controller: function($scope){
$scope.buttonClicked : function(){
// show busy indicator
$scope.onChangeData();
}
}
link: function preLink(scope, element, attrs) {
scope.$watchCollection('arrayData', function (value) {
// hide busy indicator
});
}
}
One way you can achieve this is by returning a promise for onChangeData like
$scope.onChangeData=function() {
var defer=$q.defer();
//process data
//resolve defer
return defer.promise;
}
Then in your directive controller do
$scope.onChangeData().then(function() {
//stop animation
});

Angular ng-blur not working with ng-hide

Using a directive focus-me="inTextModeInput" in a text input
app.directive('focusMe', function($timeout) {
/*focuses on input
<input type="text" focus-me="focusInput">
*/
return {
scope: { trigger: '=focusMe' },
link: function(scope, element) {
scope.$watch('trigger', function(value) {
if(value === true) {
$timeout(function() {
element[0].focus();
scope.trigger = false;
});
}
});
}
};
});
Actually having 2 inputs, both uses focus-me
When i programatically set the value to focus on an input the ng-blur of other is not called.
NOTE : i am also using this in an ng-repeat.
Isolated scope
The blur is called, but you're not seeing that because you've created a directive with an isolated scope. The ng-blur is executed on the $parent scope. You should only use an isolated scope when the directive is implementing re-useable templates.
Two way binding on trigger
The line 'scope.trigger = false' is also setting a different boolean value because it's on a different scope. If you want to assign a value to a variable from a directive you should always wrap the value inside another object: var focus = { me: true } and set it like trigger=focus.me.
A better solution
But I wouldn't set the trigger to false at all. AngularJS is a MVC/MVVM based framework which has a model state for the user interface. This state should be idempotent; meaning that if you store the current state, reload the page and restore the state the user interface should be in the exact same situation as before.
So what you probably need is a directive that
Has no isolated scope (which allows all other directives to work: ng-blur, ng-focus, ...)
Keeps track of a boolean, which indicates the focus state
Sets this boolean to false when the element has lost focus
It's probably easier to see this thing in action: working plunker.
Maybe this (other) plunker will give you some more insight on scopes and directives.
Code
myApp.directive('myFocus', function($parse, $timeout) {
return {
restrict: 'A',
link: function myFocusLink($scope, $element, $attrs, ctrls) {
var e = $element[0];
// Grab a parser from the provided expression so we can
// read and assign a value to it.
var getModel = $parse($attrs.myFocus);
var setModel = getModel.assign;
// Watch the parser -- and focus if true or blur otherwise.
$scope.$watch(getModel, function(value) {
if(value) {
e.focus();
} else {
e.blur();
}
});
function onBlur() {
$timeout(function() {
setModel($scope, false);
});
}
function onFocus() {
$timeout(function() {
setModel($scope, true);
});
}
$element.on('focus', onFocus);
$element.on('blur', onBlur);
// Cleanup event registration if the scope is destroyed
$scope.$on('$destroy', function() {
$element.off('focus', onFocus);
$element.off('blur', onBlur);
});
}
};
});

Get value of scope variable in controller AngularJS

I have to fetch value of scope variable defined in directives. I have to get value of that scope variable in controller using AngularJS. How can i fetch value of scope variable?
Directive
app.directive('checkToggle', function() {
return {
scope: true,
link: function ($scope, element, attrs) {
$(element).on('click', function() {
$(element).find('i').toggleClass('icon-check icon-check-empty');
if ($(element).find('i').hasClass('icon-check')) {
$scope.isChecked = 'true';
} else {
$scope.isChecked = 'false';
}
});
}
}
});
I have to get $scope.isChecked value in controller.
If I understand your use-case correctly you would like to toggle an icon on click. If so you don't need to write any directive for this. And provided that you would like to write a directive your shouldn't go about it as you've started. Your code is very imperative, jQuery-like while AngularJS power is in driving declarative UI based on model changes.
Anyway, toggling an icon can be easily done with standard AngularJS directives:
<i ng-class="{'icon-star' : isChecked, 'icon-star-empty': !isChecked}" ng-click="isChecked = !isChecked"></i>
Here is a working plunk: http://plnkr.co/edit/nXXQA41w00Cpeo6tTibg?p=preview

Resources