I'm new to JavaScript and Angular JS.
I have to write a custom directive to validate the content of an input field passed in an .aspx page; more precisely I have to check if an username with the value passed in the input already exists. In my directve I have to call a service that checks for duplicates in the db and afterwards depending on the result I have to perform a success callback/error callback and to return an object containing info about the validation check and a message to diplay. In particular I'm stuck on this last part regarding callbacks and returning an object with info (in my version I cannot use $.defer).
Unfortunaltely, I cannot take advantage of the already existing custom validation for forms provided by Angular (that is $asynchrounous validators and $q.defer) as I have to use an older version of Angular 1.2.26.
Could you provide me some hints/examples on how to achieve it? I'm trying hard but I still find custom directives a bit difficult and I cannot find examples for custom validation (except for those based on Angular 1.3 $asynch validators).
Thank you in advance
You can easily use $http service in your directive's link or controller function and do operations with response data. What is the problem exactly can you be more clear?
You can write your own directive for this: example
app.directive('asyncNameCheck', function($http){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elm, attr, ngModel){
elm.bind('input', function(){
ngModel.$setValidity('asyncValid', false);
$http.get('http://graph.facebook.com/facebook')
.success(function(data){
ngModel.$setValidity('asyncValid', true);
});
})
}
}
})
and in the html
<form name="myform">
<input name="testfield" async-name-check required ng-model="test"/>
<p ng-show="myform.testfield.$error.asyncValid">This field is invalid</p>
</form>
Just note that this directive is not a reusable one but works for your requirements. Do not do this in the controller, I've spent a lot of time with angular validation and if you're controlling validation in the controller, it will cause you alot of issues later on.
Related
Building a small search app using Elasticsearch and AngularJS. 2 pages, home and results page. Everything is working except... I have this custom search directive that I'm trying to pass the value of a service into. The service is a variable that is bound to ngModel in my controller.
How can I pass the value of searchTerms from home to the results page?
My service
.service('queryService', function() {
var searchTerms;
this.searchTerms = null;
I pass my service into the controller and set it to $scope
$scope.searchTerms = queryService.searchTerms;
I then $watch it for changes
$scope.$watch('searchTerms', function() {
queryService.searchTerms = $scope.searchTerms;
});
My directive looks like this
.directive('searchResults', ['queryService', function(queryService) {
return {
restrict: 'E',
replace: true,
//priority: 1001,
scope: {
searchTerms: "=",//ngModel
results: "=",
websiteUrls: "=",
suggestions: "&",
search: "&"
},
templateUrl: 'search/search-results.html',
link: function(scope, element, attrs) {
}
}
}]);
My search input:
<input type="text" name="q" ng-model="searchTerms" placeholder="Search" class="form-control input-lg" id="search-input" uib-typeahead="query for query in getSuggestions($viewValue)" typeahead-on-select="search($item)" autofocus>
I have 2 way data binding working, but no autocomplete(Angular UI Bootstrap Typeahead) or search functionality. I'm pretty sure something is suppose to go in the link function, just not sure what... still learning AngularJS directives.
NOTE: Everything works if I perform a search from the results page.
More Info So basically what I'm trying to do is a user enters a search Term on the home page. searchTerms is my ngModel. I'm using AngularJS UI Bootstrap Typeahead for autocomplete functionality(as can be seen on the input tag). I have a queryService that initiates searchTerms to null and the queryService is DI into the controller. I have a directive that has isolate scope (scope: {}), where I am passing searchTerms, the results object and both the autocomplete and search functions. I'm using ngRoute for now because I'm trying to keep this simple until I have it working - its only 2 pages.
HTML Snippet
<search-results ng-model="searchTerms" website-urls="page" results="results" uib-typeahead="query for query in getSuggestions($viewValue)" typeahead-on-select="search($item)"></search-results>
Light Bulb moment, maybe
As I continue to learn more about directives, I think I just solved the problem. I had all this working initially without the use of a directive. Using routes, templates and controllers. I should just be able to use my current controller in my directive, right?
The only reason why I want to use a directive is because it seems to be the best option when AngularJS is used in combination with a CMS.
Am I on the right track now?
After further reading (and understanding), it seems that I can use the controller I already have in my directive and that should solve this problem quite nicely. I will post results after completing and testing it.
I have to get the value of an input text with AngularJS but without using Controller.
How i can get this value?
I saw this posts but uses .controller
link post
You can use this code, with angular.element:
angular.element(document.querySelector('#your_input_tag_id')).val();
or, with simple jQuery:
$('#your_input_tag_id').val();
make your input a model and the value will always be available as the model
<input type="text" ng-model="myModelName">
and then your value will be available within the scope of your module as myModelName
console.log('value = ' + $scope.myModelName);
, if you are trying to get the value from somewhere other than the scope of the module or app then you are probably doing it wrong and not the angular way, it really is best to use a controller or link function even if it's the main app controller or link function, and you would be well placed to push back on any requirement not to use a controller (which sounds like a bad or misunderstood requirement).
Rather than querying the DOM for elements (which isn't very angular see How do I "think in AngularJS" if I have a jQuery background?) you should perform your DOM manipulation within your directive. The element is available to you in your link function.
So in your myDirective
return {
link: function (scope, element, attr) {
element.html('Hello world');
}
}
If you must perform the query outside of the directive then it would be possible to use querySelectorAll in modern browers
angular.element(document.querySelectorAll("[my-directive]"));
$('#your_input_tag_id').val();
however you would need to use jquery to support IE8 and backwards
angular.element($("[my-directive]"));
I'm struggling with a particular case for validation. I have to do cross-field validation with what is essentially a more complicated multi-select directive. The problem is that this is a very general directive and will not necessarily need validation in every case nor will it need the same validation.
simplifiedSelect.tpl.html
<select name="vm.name" ng-options='p in data' ng-model='vm.model'>
simplifiedSelect.js
(function() {
angular
.module('myApp')
.directive('simplifiedSelect', simplifiedSelect)
function simplifiedSelect(){
var directive = {
restrict='E',
scope:{
secondVar:'='
},
controller:Controller,
controllerAs:'vm',
templateUrl: '/simplifiedSelect.tpl.html'
}
Controller.$inject('$scope')
function Controller($scope) {
//do stuff
}
}());
Every example that I have seen seems to require that the validation directives are attached directly to the input, not to the directive itself, and attempting to attach the validation to the directive led to having no result, even when I attached and required ngModel to the outer directive. I have been able to get a basic custom validation to work on another field, but this is just beyond me.
I am trying to call (or use) few custom directives in ionic framework, dynamic is like <mydir-{{type}} where {{type}} will come from services and scope variable, having values radio, checkbox, select etc, and created my directives as mydirRadio, MydirCheckbox, mydirSelect, But its not working.
Is their any good approach to get the dynamic html as per {{type}} in scope?
Long story short; no you can't load directives dynamically in that way.
There are a few options for what you can do. You can, as other answers have mentioned, pass your context as an attribute (mydir type="checkbox"). You could make a directive that dynamically loads another directive, as also mentioned by others. Neither of these options are imo every good.
The first option only works if you write the directive yourself, not when using something like ionic. It also requires you to write multiple directives as one, which can get very messy very quickly. This mega directive will become hard to test and easy to mess up when maintaining it in the future. Note that this is the correct way to pass data to a directive from the view, it's just not good for this specific use case.
The second option is problematic because obfuscates things a bit too much. If someone reads your html and sees a directive called dynamic that is given dynamic data... they have no idea what is going to happen. If they see a directive called dropdown that is given a list they have a fair idea of what the result will be. Readability is important, don't skimp on it.
So I would suggest something simpler that requires much less work from you. Just use a switch:
<div ng-switch="type">
<mydir-select ng-switch-when="select"></mydir-select>
<mydir-checkbox ng-switch-when="checkbox"></mydir-checkbox>
</div>
I dont understand why do you need dynamic directives.
Simple use single directive and change the template accordingly.
For example -
angular.module('testApp')
.directive('dynamicDirective', function($compile,$templateCache,$http) {
return {
restrict: 'C',
link: function($scope,el) {
//get template
if(radio){
$http.get('radio.html', {cache: $templateCache}).success(function(html){
//do the things
el.replaceWith($compile(html)($scope));
});
} else if(checkbox){
//load checkbox template
} //vice-versa
}
};
});
You can inject service variable in directive also.
a bit more code would help. I don't know, if its possible to do dynamic directives like the ones in a tag
<{dyntag}></{dyntag}>
but you also can use an expression like
<your-tag dynamic_element="{type}">...</your-tag>
which should have exactly the same functionality. In your case it would be like:
Your JSObject ($scope.dynamics):
{"radio", "checkbox", "select"}
and your HTML:
<div ng-repeat="dyn in dynamics">
<your-tag dynamic_element="{dyn}"></your-tag>
</div>
Yes, that's not a problem. You can interpolate your data using {{}} and in your directive compile a new element using that data:
myApp.directive('dynamic', function($compile, $timeout) {
return {
restrict: "E",
scope: {
data: "#var" // say data is `my-directive`
},
template: '<div></div>',
link: function (scope, element, attr) {
var dynamicDirective = '<' + scope.data + ' var="this works!"><' + scope.data + '>';
var el = $compile(dynamicDirective)(scope);
element.parent().append( el );
}
}
});
HTML:
<div ng-controller="MyCtrl">
<dynamic var="{{test}}"></dynamic>
</div>
Fiddle
I'm working on understanding how to use Angular Directives to implement front-end validations. While I'm familiar with the way that directives generally work, what I'm having a hard time finding in any tutorial, blogpost, or even the actual Angular docs, is how to meaningfully implement a useful validation. (By that, I mean one that isn't already there, as in this basic tutorial for how to implement a validation attribute.
The most common of these in my experience, is a 'Required If' scenario. If I have Text Field A and Text Field B in my form, and Text Field A has a value, my business rules tell me that Text Field B must have a value. However, the various tutorials for validation directives have all only relied on the element that they are tied to.
Question: I suspect that I am approaching the problem of how to implement something like a Required If validation completely wrong. In the Angular way, what is the correct way to require a value in a form if and only if a field that it's dependent on has a value?
You could try using ng-if to add the next form element only if the previous form element is valid. The second element won't exist in the DOM unless the first element is valid.
I did some additional experimentation today. A coworker prompted me with a different situation, that finally led to me solving the problem for myself.
I was fundamentally looking at the problem in the wrong way, for starters - specifically, I was looking at it in the jQuery way, by expecting the directive to somehow expose ways to read individual form elements. This isn't necessarily correct, since we have a scope that we can evaluate.
Here is the pre-1.3 Angular directive code:
var app = angular.module('app', []);
app.controller('someController', [
'$scope',
function($scope) {
$scope.valueA = '';
$scope.valueB = 'Chicken';
}
]);
app.directive('requiredIf', [
function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attr, model) {
// Read the companion attribute 'otherModelName'
var otherModelName = attr.otherModelName;
scope.$watch(attr.ngModel, function (value) {
var isValid = checkAgainstDependentModel(value);
model.$setValidity('requiredIf', isValid);
});
function checkAgainstDependentModel(value) {
// Assumes that the scope exposes a property with the passed-in
// model name as its name
var field = scope[otherModelName];
var isValid = true;
if(field != null || field != '')
if(value == null || value == '')
isValid = false;
return isValid;
}
}
};
}
]);
...In markup we would use it like so:
<form name='someForm'>
<input type='text' name='mainField' ng-model='valueA' />
<input type='text' name='subordinateField' ng-model='valueB' required-if other-model-name='valueA' />
<span style='color=red' ng-if='someForm.subordinateField.$error.requiredIf'>
(Required!)
</span>
</form>
This pattern can be expanded to various other custom validations, pre-1.3. My research has shown me that Angular 1.3 will remove the $parsers and $formatters in favor of $validators and $asyncValidators.
EDIT: Instead of using $formatters/$parsers, a better idea I ran across is to instead do a scope.$watch on the associated ngModel. Since this is a pre-1.3 implementation, we can still just do model.$setValidity('thing', isValid); This is based on the answer to the question of how best to achieve remote validation pre-1.3.