Directive improvement with watch and conditional templating - angularjs

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>',

Related

Value undefined in angular custom directive

I tried to make a directive for destroying DOM elements if user doesn't have the permission to see it. I do this as follows:
angular
.module('digital_equation.auth')
.controller('AuthLoginController', AuthLoginController)
.directive('ngPermission', ngPermissionDirective);
function ngPermissionDirective() {
return {
multiElement: true,
scope: {
ngPermission: '#'
},
restrict: 'A',
link: function (scope, element, attributes) {
scope.$watch('ngPermission', function (value) {
console.log(value);
//Access rootScope to get the current user permissions
var permissions = scope.$root.globals.currentUser.role.settings.permissions;
//Loop through permissions object to check if any permission is the same as the value sent to directive
var keepGoing = true;
angular.forEach(permissions, function (permission, key) {
if (keepGoing == true)
{
if (key == value) {
element.show();
keepGoing = false;
}
else {
element.hide();
}
}
});
})
}
};
}
HTML:
<div class="col-xs-12 col-md-9" ng-permission="manage_appointments"></div>
In this situations for example if the current user taken from rootScope doesn't have the permission "manage_appointments" among it's permission this div should be destroyed. As you can see I only know how to hide it but this is not enough I need it to be destroyed so on page inspect this div doesn't show up.
My second problem is that console.log(value); returns undefined not matter what I tried.
And I also need an advice upon accessing rootScope. If I pass the rootScope as parameter here it works but I cannot pass the scope as well.. so how can I do this.
link: function(rootScope, element, attributes )
Please keep in mind that even though I declare my directive in the authController I need it to be available in my entire project.
Sorry for the description but it is my first custom directive in AngularJs and I tried a lot of options.
Thank you all for your time and help!
Edit:
scope.$watch('ngPermission', function (value)
This solved my problem with value being undefined but when I try to use the directive on two different elements (one to be hidden and one to be shown) it will do whatever the last use of my directive is applied (show both in this case). Any ideas why this happens?
Solution:
function ngPermissionDirective() {
return {
multiElement: true,
priority: 900,
scope: {
ngPermission: '#'
},
restrict: 'A',
link: function (scope, element, attributes) {
scope.$watch('ngPermission', function (value) {
console.log(value);
//Access rootScope to get the current user permissions
var permissions = scope.$root.globals.currentUser.role.settings.permissions;
//Loop through permissions object to check if any permission is the same as the value sent to directive
var keepGoing = true;
angular.forEach(permissions, function (permission, key) {
if (keepGoing == true)
{
if (key == value) {
element.show();
keepGoing = false;
}
else {
element.remove();
}
}
});
})
}
};
}
Try changing scope.$watch(attributes.ngPermission, ... to scope.$watch('ngPermission', ....
An alternative approach might be $observe - attributes.$observe('ngPermission', ...

Get Rendered Form into $StateChangeStart

Is there any way that I can get the rendered form into
$rootScope.on("$stateChangeStart", function (){
})
I tried two things.
First: Using $template Request I got the template using templateURL and compiled that but it renders predefined template not the DOM's rendered.
See the code
if (fromState.name.length > 0) {
$templateRequest(fromState.templateUrl)
.then(function (html) {
var compiledElement = $compile(html)($rootScope);
var compliedForm = compiledElement.find('form');
}
}
then Secondly, I tried using
angular.element('document').find('form');
But it gives me list of attribute and all. But how to get check form is valid or not.
Document
I think what you are trying to achieve, is to block a state change when a form in the current view is not valid. I would make a directive for this, something like:
app.directive("formValidStateCheck", function($rootScope) {
return {
restrict: "A",
require: "ngForm",
link: function(scope, element, attrs, ngFormCtrl) {
$rootScope.$on("$stateChangeStart", function(event) {
if (ngFormCtrl.$invalid) {
// prevent routing
if (!confirm("Are you sure"))
event.preventDefault();
}
}
});
}
}
});
Than put the directive on your forms:
<form ng-form="myForm" form-valid-state-check>
</form>
.find() method will not work with selectors and tag names. you need to get it by form id(for this have a id to the form).
Then use angular.element(document.getElementById("#form_id"));

angular set dirty form within directive controller

I have an angular driven form using $dirty to spot for changes to enable/disable submit button.
Part of the form uses a Directive for uploading a logo but the form is noticing this as a changed element so upon setting a logo that validates in size I need to manually trigger that the form has had a change so should be a case of formName.$setDirty(); however console is saying that $setDirty() is not defined and I think this is because I am within a directive.
Within my directives controller upon file selection I call the function below and it is here when the file is valid that I would want to call the setdirty method.
function isFileValid(file) {
vm.fileValid = true;
vm.errorMessage = "";
if (file.size > 16777216) {
vm.errorMessage = "The File is too big!";
vm.fileValid = false;
} else if (file.size == 0) {
vm.errorMessage = "The File is empty!";
vm.fileValid = false;
}
if (vm.fileValid) {
// make form dirty
$setDirty();
}
return vm.fileValid;
}
Here is the directive JS
(function () {
'use strict';
.module("tpBusinessProfile")
.directive("tpLogoUploader", tpLogoUploader);
function tpLogoUploader() {
return {
templateUrl: "tpLogoUploader.directive.html",
bindToController: true,
scope: {
changedMethod: "&"
},
controller: "tpLogoUploaderCtrl",
controllerAs: 'logoCtrl',
restrict: "E"
};
}
})();
Any help is appreciated.
You need to use directive require option and require controller of form directive:
{
require: '^form'
and then in link function bind method that you need to your scope (dirty solution):
link(scope, elem, attrs, formController){
scope.makeFormDirty = formController.$setDirty
}
and now you can use it in your controller via makeFormDirty

prevent api call from execution repeatedly in directive

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.

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);
});
}
};
});

Resources