autofocus doesn't work when used with ng-include - angularjs

I want to set focus to one of the input box in partial view
like .
and including this by
This is working fine when page loads for the first time. but when I change the partials autofocus doesn't work .
I believe it is because of autofocus work on pageload how can it make work here

I am not sure how to fix the problem of page reload but I think we can work out another solution here. I wrote a small directive once to conditionally set focus on an input element.
Here is the code:
function SetFocusDirective($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var model = $parse(attrs.setFocus);
scope.$watch(model, function (value) {
if (value === true) {
element[0].focus();
}
});
element.bind('blur', function () {
scope.$apply(model.assign(scope, false));
});
}
};
}
SetFocusDirective.$inject = ['$parse'];
app.directive('setFocus', SetFocusDirective);
And here is how you can use it:
<input type="text" ng-model="firstName" set-focus="autofocusFirstName">
Where autofocusFirstName is a $scope variable which should have a boolean value.
So every time your partials load, all the directives inside them will get linked and do their jobs. If you end up using this directive, you should be able to achieve what you want.

Related

Why does adding additional AngularJS validation directives cause $asyncValidators to run multiple times?

Why does adding additional AngularJS validation directives cause $asyncValidators to run multiple times on page load?
I created a custom directive which implements $asyncValidators. This is the basic structure of that custom directive:
myApp.directive('userSaved',['$q','userLookup',function($q, userLookup){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ctrl){
ctrl.$asyncValidators.userSaved = function(modelValue, viewValue) {
// do stuff
}
}
}
}]);
The controller initializes the tailNumber model value like this:
$scope.tailNumber = 'N33221';
This is the html where the user-saved directive runs 3 times on page load:
<input ng-model="tailNumber" name="tailNumber" user-saved
ng-minlength="2" ng-pattern="/^[A-z][a-zA-Z0-9]*$/" >
When I remove ng-minlength="2" then the user-saved directive runs twice on page load (2 times). This is the html with ng-minlength="2" removed:
<input ng-model="tailNumber" name="tailNumber" user-saved
ng-pattern="/^[A-z][a-zA-Z0-9]*$/" >
When I remove ng-pattern="/^[A-z][a-zA-Z0-9]*$/" then the user-saved directive only runs 1 time. This is the html after removing ng-pattern="/^[A-z][a-zA-Z0-9]*$/"
<input ng-model="tailNumber" name="tailNumber" user-saved >
Why does my function registered with $asyncValidators run an additional time for each additional ng validator attached to the form element?
My custom directive is an expensive $http call, and I prefer my custom directive only run once on page load. Is it possible to use all of these ng validators and while only running my async validator function one time instead of 3 times on page load?
This is because validation directives like ngMaxlength, ngPattern invoke an initial validation cycle with a call to ngModelController.$validate().
This causes all the validation directive to run their validation logic, including the async validators.
One way to prevent the redundant $http calls, and in fact it is a good practice anyway, is to cache the validation result for each input.
It actually took me a while to figure this one out. As mentioned in this post, Angular validators trigger additional validations. I decided not to fight this behavior and work around it instead, falling back to parsers and formatters:
myApp.directive('userSaved',['$q','dataservice',function($q, dataservice){
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ctrl){
ctrl.$parsers.unshift(checkUserSaved);
ctrl.$formatters.unshift(checkUserSaved);
function checkUserSaved(value){
ctrl.$setValidity("usersaved") // the absence of the state parameter sets $pending to true for this validation
dataservice.getUserSaved(value).then(function(response){
var userIsSaved = (response === true);
ctrl.$setValidity("usersaved", userIsSaved); // the presence of the state parameter removes $pending for this validation
return userIsSaved ? value : undefined;
});
return value;
}
}
}
}]);
As a reference, you also might want to check the Angular docs
EDIT
Upon further investigation, it appears that in the case of ng-pattern the extra validations are only triggered when the regex is converted from a string.
Passing the regex directly:
<div ng-pattern="/^[0-9]$/" user-saved></div>
fixed the problem for me while making use of the validators pipeline.
For reference, see this github issue
I followed #New Dev's advice and implemented a simple caching routine which fulfilled my requirement quite nicely, here's what I came up with ..
link: function (scope, element, attributes, ngModel) {
var cache = {};
ngModel.$asyncValidators.validateValue = function (modelValue) {
if (modelValue && cache[modelValue] !== true) {
return MyHttpService.validateValue(modelValue).then(function (resolved) {
cache[modelValue] = true; // cache
return resolved;
}, function(rejected) {
cache[modelValue] = false;
return $q.reject(rejected);
});
} else {
return $q.resolve("OK");
}
};
}

Keeping Angular form validation DRY

I have a form that has maybe 15-20 fields. Each one of them contains an attribute that looks something like this:
ng-class='addressForm.city.$invalid && addressForm.city.$touched ? "error" : ""'
So I have that long string of code repeated 15-20 times. This sets my DRY alarm bells off bigtime.
I could devise my own way to make this more DRY but I don't want to re-invent the wheel. Is there a gererally-accepted way of keeping Angular form validation DRY?
If it's just for styling use CSS.
Angular will add .ng-invalid and .ng-touched to input elements that are invalid and touched.
Or you could wrap the whole thing in a directive something like
angular.module('module').directive('errorClass', function(){
return{
require: 'ngModel',
link: function(scope, el, attr, model) {
function setClass() {
if(model.$touched && model.$invalid) {
if(!el.hasClass('error')) {
el.addClass('error');
}
} else {
el.removeClass('error');
}
}
scope.$watch(function(){ return model.$touched; }, setClass);
scope.$watch(function(){ return model.$invalid; }, setClass);
}
}
});
Also i havn't actually used this directive, so it may need some tweaking.
As #lain said, you don't have to add another class (like error) if the field is invalid, Angular adds that for you by default, it's just the name that differs (ng-invalid).
You can see how is that used here (the official form example from Angular).
If you still want to do this in your way, this is the implementation of my latest comment, using ngChange directive.
The html:
<input type="text" ng-model="addressForm.city" required ng-change="fieldChanged(this, 'city')">
The change event:
$scope.fieldChanged = function(el, fieldName){
if($scope.addressForm[fieldName].$invalid && $scope.addressForm[fieldName].$touched) angular.element(el).addClass('error');
else angular.element(el).removeClass('error');
}
This is not good practice (to manipulate the DOM in the controller), you should implement that in a directive, but binding a directive to each field would add watchers and I, personally, try to avoid as much as possible using too many watchers.
A more elegant option would be to combine this ngChange with ngClass or simply go with that simple DOM manipulation within controller. It's your choise :)
I ended up creating my own directive for this. I believe the following directive, when applied, will behave equivalently to this:
form(name='addressForm')
input(
type='text'
name='city'
ng-class='addressForm.city.$invalid && (addressForm.city.$touched || addressForm.$submitted) ? "error" : ""'
)
Instead of all that, I can do:
form(name='addressForm')
input(
type='text'
name='city'
validate-for='addressForm'
)
The directive will check validity on:
Blur
Form submission
Value change
Here's the code (ES6):
'use strict';
class ValidateFor {
constructor() {
this.restrict = 'A';
this.require = 'ngModel';
this.link = ($scope, $element, $attrs, ngModel) => {
var form = $scope[$attrs.validateFor];
var field = form[$element.attr('name')];
$scope.$on('form-submitted', () => {
this.checkForErrors(field, $element);
});
$scope.$watch(() => ngModel.$modelValue, () => {
if (field.$touched) {
this.checkForErrors(field, $element);
}
});
$element.bind('blur', () => {
this.checkForErrors(field, $element);
});
};
}
checkForErrors(field, $element) {
if (field.$invalid) {
$element.addClass('error');
} else {
$element.removeClass('error');
}
}
}
ValidateFor.$inject = [];
You could probably even eliminate the necessity for supplying the form name in validate-for. I just did it that way because I have some nested form situations.
valdr looks great. I haven't used it yet, but I will try it, and will update this post later.

Angular binding does not work in data- attribute

I am using some css html template that comes with many html components and with lots of data-attributes for various things. For example for slider it has something like
<div class="slider slider-default">
<input type="text" data-slider class="slider-span" value="" data-slider-orientation="vertical" data-slider-min="0" data-slider-max="200" data-slider-value="{{ slider }}" data-slider-selection="after" data-slider-tooltip="hide">
</div>
Here I am trying to bind the value
data-slider-value="{{ slider }}"
But it's not working. Variable 'slider' is set in the $scope as:
$scope.slider = 80;
Same value 80 shows up right when I bind it as:
<h4>I have {{ slider }} cats</h4>
I have also tried
ng-attr-data-slider-value="{{ slider }}"
It didn't work.
Update
The directive has something like this
function slider() {
return {
restrict: 'A',
link: function (scope, element) {
element.slider();
}
}
};
where element.slider(); calls the code in bootstrap-slider.js (from here) for each of the sliders.
I played with this for a while, and came up with a few options for you. See my Plunkr to see them in action.
Option 1: No need to update the scope value when the slider changes
This will work with the HTML from your question. The following is what you should change the directive code to.
app.directive('slider', function slider() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
attrs.$observe('sliderValue', function(newVal, oldVal) {
element.slider('setValue', newVal);
});
}
}
});
Option 2: Two way binding to the scope property
If you need the scope property to be updated when the slider handle is dragged, you should change the directive to the following instead:
app.directive('sliderBind', ['$parse',
function slider($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var val = $parse(attrs.sliderBind);
scope.$watch(val, function(newVal, oldVal) {
element.slider('setValue', newVal);
});
// when the slider is changed, update the scope
// property.
// Note that this will only update it when you stop dragging.
// If you need it to happen whilst the user is dragging the
// handle, change it to "slide" instead of "slideStop"
// (this is not as efficient so I left it up to you)
element.on('slideStop', function(event) {
// if expression is assignable
if (val.assign) {
val.assign(scope, event.value);
scope.$digest();
}
});
}
}
}
]);
The markup for this changes slightly to:
<div class="slider slider-default">
<input type="text" data-slider-bind="slider2" class="slider-span" value="" data-slider-orientation="vertical" data-slider-min="0" data-slider-max="200" data-slider-selection="after" data-slider-tooltip="hide" />
</div>
Note the use of the data-slider-bind attribute to specify the scope property to bind to, and the lack of a data-slider-value attribute.
Hopefully one of these two options is what you were after.
I use .attr and it works for me. Try this:
attr.data-slider-value="{{slider}}"

AngularJS - how to focus on an element via a controller

I know the question has been asked multiple time, but I can't seem to find anywhere how to focus to an element from within a controller. What is the best approach? Would it be better to do a directive? But if so, then how would I call it within my controller? Or is it better to create a service then?
What I already have and works properly from within HTML code is a directive:
.directive('ngxFocus', ['$timeout', function($timeout) {
return function(scope, element, attr) {
$timeout(function () {
element.focus();
}, 10);
};
}])
Can I call directive within controller? I'm still learning AngularJS and I'm a bit confused on what the best approach is in this case. I really want to do it via the controller, at the moment I use a simple 1 line of jQuery to focus, but yeah it's not the Angular way and so I'd like to go with the correct way.
Note
To be more specific with an example, let say I have 10 inputs in the HTML and let say that inside the execution of a function (defined in the controller), I want to focus on 1 of the multiple inputs directly from the function (again this is all declared inside the controller). I would rather not write anything inside the HTML code, if possible, but instead call a focus function or something that will focus to the input I chose. I know I could write it simply in jQuery with $('input12').focus(); but I want to know how to do it the AngularJS way. All the answers I get so far are based on writing a Directive, that also equals to writing something inside the HTML, isn't there any other way???
Example
For more explicit example of my form... I have a first input connected to a Yahoo web service (stock market), this input filled by the user will hold a stock quotes symbol that can be anywhere in the world and then the user will choose (from a dropdown) his Bank account... now from there, my controller will check that the stock quotes market is in the same currency as the user's bank account (ex.: GOOG is US currency, if user's account is in $CAD, it will fail because GOOG is in $USD). If currency isn't the same, I want to advise my user and to do so I would seriously prefer to focus on the field so he could change his symbol if he made an error.
I you're trying to work with elements in controller, be sure you're going wrong, the controller's target in to bind data received from services to view, not to manipulate view.
If you want to focus on an element with route change:
app.directive('focuser', ['$location', function ($location) {
return {
restrict: 'A',
link: function ($scope, $element) {
$scope.$watch(function () {
//simply focus
$element.focus();
//or more specific
if ($location.$$url == '/specific/path') {
$element.focus();
}
});
}
};
}]);
I've made this directive:
app.directive('rfocus',function(){
return {
restrict: 'A',
controller: function($scope, $element, $attrs){
var fooName = 'setFocus' + $attrs.rfocus;
$scope[fooName] = function(){
$element.focus();
}
},
}
});
It adds to controller's $scope function to set focus on element. Name of the function is based on value given in attribute.
Using: <input type="text" rfocus="Input1"/> will create function setFocusInput1() which you can use in your controller.
Here is the fiddle: http://jsfiddle.net/aartek/2PJMQ/
I've recently started to learn Angular, too, but hopefully I can provide you a different way of approaching this.
I've been using some basic jQuery to focus, too, so in that regard, I can't really help you. However, with regard to calling a directive within a controller, I can't find any articles that say "yes, you can", or "no, you can't". I know that you can declare a controller within a directive, though, so you miiiiight be able to do something like this:
.directive('ngxFocus', function() {
return {
restrict: 'A',
controller: //do your controller stuff here
}])
I know it's an old question, but here's an other approach, where you set to true a variable in the controller, and that's this action that set the focus to your element.
Try this:
myAngularModule.directive('goFocus', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.goFocus, function (newValue) {
if (newValue) {
$timeout(function () {
element[0].focus();
}, 100);
}
});
element.bind("blur", function (e) {
$timeout(function () {
scope.$apply(attrs.goFocus + "=false");
}, 10);
});
element.bind("focus", function (e) {
$timeout(function () {
scope.$apply(attrs.goFocus + "=true");
}, 10);
});
}
}
}]);
In HTML template:
<input go-focus="focusvar1" type="text" ng-model="mytext1" />
<input go-focus="focusvar2" type="text" ng-model="mytext2" />
<input go-focus="focusvar3" type="text" ng-model="mytext3" />
<button ng-click="doFocus()">OK</button>
In javascript angular controller:
myAngularModule.controller('myController', function () {
var self = this;
self.doFocus = function () {
// do some logic and focus your field
self.focusvar2 = true;
};
});

AngularJS - setting focus to element using NON-ISOLATE directive

I know this question has been asked about 100 times (trust me, I've read them all), but I'm having trouble getting focus to go to an input box when the directive does NOT use isolate scope. The scope.$watch doesn't fire when the backing data changes.
Why not just use the one with isolate scope, you ask? Well, my understanding is that you should ONLY use isolate scope if your directive has a template.
The only differences in the directives is:
// works
app.directive('doesFocus', function ($timeout) {
return {
scope: { trigger: '#doesFocus' },
link: function (scope, element) {
scope.$watch('trigger', function (value) {
// sets focus
}
...
// does not work, and in fact when I inspect attrs.doesNotFocus it is undefined
app.directive('doesNotFocus', function ($timeout) {
return {
scope: false,
link: function (scope, element, attrs) {
scope.$watch(attrs.doesNotFocus, function (value) {
// sets focus
}
...
I'm on week 3 of using Angular, so I must be missing some silly semantic issue.
Here is a fiddle illustrating my issue.
http://jsfiddle.net/tpeiffer/eAFmJ/
EDIT
My actual problem was that my real code was like this (hazard of mocking the problem, you sometimes mask the real problem):
<input should-focus="{{isDrawerOpen()}" ... ></input>
but because I was using a function, not a property, I was missing the required ticks
<input should-focus="{{'isDrawerOpen()'}}" ... ></input>
Making this change fixed the problem and my directive can still be like this:
scope.$watch(attrs.shouldFocus, focusCallback(newValue));
END EDIT
Thanks for helping me in my quest for angular excellence!
Thad
Remove {{}} from your HTML. So instead of:
<input class="filter-item" placeholder="Enter filter"
does-not-focus="{{bottomDrawerOpen}}" type="text">
use
<input class="filter-item" placeholder="Enter filter"
does-not-focus="bottomDrawerOpen" type="text">
Then it works with watching attrs.doesNotFocus:
scope.$watch(attrs.doesNotFocus, function (value) {...} );
Fiddle
Your bottom drawer was watching a function isDrawerOpen(), not a property.
Change
scope.$watch('isDrawerOpen()',...);
to
scope.$watch('toggleBottomDrawer',...);

Resources