56

How to autocapitalize the first character in an input field inside an AngularJS form element?

I saw the jQuery solution already, but believe this has to be done differently in AngularJS by using a directive.

Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265
Federico Elles
  • 4,659
  • 7
  • 27
  • 35

15 Answers15

100

Yes, you need to define a directive and define your own parser function:

myApp.directive('capitalizeFirst', function($parse) {
   return {
     require: 'ngModel',
     link: function(scope, element, attrs, modelCtrl) {
        var capitalize = function(inputValue) {
           if (inputValue === undefined) { inputValue = ''; }
           var capitalized = inputValue.charAt(0).toUpperCase() +
                             inputValue.substring(1);
           if(capitalized !== inputValue) {
              modelCtrl.$setViewValue(capitalized);
              modelCtrl.$render();
            }         
            return capitalized;
         }
         modelCtrl.$parsers.push(capitalize);
         capitalize($parse(attrs.ngModel)(scope)); // capitalize initial value
     }
   };
});

HTML:

<input type="text" ng-model="obj.name" capitalize-first>

Fiddle

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 1
    could you explain a little more how this works or provide links to the docs? Also if you type a lowercase letter at the start and the box is not empty the cursor moves to the end, but if you type an uppercase character it doesn't... – Jason Goemaat Jun 15 '13 at 08:56
  • @JasonGoemaat, http://docs.angularjs.org/guide/forms#customvalidation I don't have any ideas on how to fix the cursor movement. – Mark Rajcok Jun 15 '13 at 23:16
  • 2
    @JasonGoemaat The cursor movement occurs because the content is being replaced by the changed text (only changed when first letter is entered as non-capital). To fix this you can capture the selection range and reset the new selection range within the new text once it is set. There is a sweet library called Rangy (https://code.google.com/p/rangy/) that will help you with this if you are serious enough about it to fix it. – James Dec 09 '13 at 22:20
  • 1
    scope[attrs.ngModel] wont work for: as attrs.ngModel evaluates to "myObj.user.name" so scope[attrs.ngModel] evaluates to undefined. – Andi Aug 05 '14 at 10:02
  • 1
    @Andi, good catch. I updated the answer and fiddle to use $parse, which will handle the original case and your scenario. – Mark Rajcok Aug 07 '14 at 21:46
  • Can someone explain why this is better (or worse) than [setting a `$watch` in the controller](http://stackoverflow.com/a/16388607/901048)? Assuming one only needs to use it one time. – Blazemonger Sep 15 '14 at 15:02
  • That does not play well with `$asyncValidators` or `ng-model-options="{ debounce: 1000 }"`. – xpepermint Sep 26 '14 at 23:47
  • when using $setViewValue, you trigger the parsers and validators again. If you add a console.log statement at the beginning of your capitalize function, you'll see it printed twice – marc carreras Apr 22 '15 at 11:37
  • I changed the capitalized variable so it would lowercase the rest of the string: var capitalized = inputValue.charAt(0).toUpperCase() + inputValue.substring(1).toLowerCase(); – Nicholas May 18 '15 at 13:19
54

Please remember that not everything needs an Angular solution. You see this a lot with the jQuery crowd; they like to use expensive jQuery functions to do things that are simpler or easier to do with pure javascript.

So while you might very well need a capitalize function and the above answers provide that, it's going to be a lot more efficient to just use the css rule "text-transform: capitalize"

<tr ng-repeat="(key, value) in item">
    <td style="text-transform: capitalize">{{key}}</td>
    <td>{{item}}</td>
</tr>
beardedlinuxgeek
  • 1,652
  • 16
  • 26
  • 13
    The OP only wants to capitalize the first letter in the input. This solution capitalizes each word – Chris Bier Oct 08 '13 at 16:35
  • 3
    good thoughts, but this wouldn't necessarily work for input fields, if it is submitted (enter input, click save, for instance), I just got bitten by that. – oma Jun 19 '14 at 19:25
  • 2
    In my case, I landed on this question looking for a "force lowercase" solution. Thanks for posting this even though it wasn't a perfect solution to the OP. – adamdport Jun 26 '14 at 20:39
  • Just what i needed!! Thanks! – Tushar Shukla Dec 17 '16 at 07:14
23

You can create a custom filter 'capitalize' and apply it to any string you want:

 <div ng-controller="MyCtrl">
     {{aString | capitalize}} !
</div>

JavaScript code for filter:

var app = angular.module('myApp',[]);

myApp.filter('capitalize', function() {
    return function(input, scope) {
        return input.substring(0,1).toUpperCase()+input.substring(1);
    }
});
Yuriy Shapovalov
  • 379
  • 1
  • 3
  • 8
4

Use the CSS :first-letter pseudo class.

You need to put everything lowercase and after apply the uppercase only to the first letter

p{
    text-transform: lowercase;
}
p:first-letter{
    text-transform: uppercase;
}

Here's an example: http://jsfiddle.net/AlexCode/xu24h/

AlexCode
  • 4,055
  • 4
  • 33
  • 46
  • 1
    No, I'm afraid not. This applies to content, not to specific properties of an element. On Inputs I'm afraid you have to use javascript. – AlexCode Jun 12 '14 at 11:49
4

Modified his code to capitalize every first character of word. If you give 'john doe', output is 'John Doe'

myApp.directive('capitalizeFirst', function() {
   return {
     require: 'ngModel',
     link: function(scope, element, attrs, modelCtrl) {
        var capitalize = function(inputValue) {
           var capitalized = inputValue.split(' ').reduce(function(prevValue, word){
            return  prevValue + word.substring(0, 1).toUpperCase() + word.substring(1) + ' ';
        }, '');
           if(capitalized !== inputValue) {
              modelCtrl.$setViewValue(capitalized);
              modelCtrl.$render();
            }         
            return capitalized;
         }
         modelCtrl.$parsers.push(capitalize);
         capitalize(scope[attrs.ngModel]);  // capitalize initial value
     }
   };
});
Pradeep Mahdevu
  • 7,613
  • 2
  • 31
  • 29
4

I would prefer a filter and directive. This should work with cursor movement:

app.filter('capitalizeFirst', function () {
    return function (input, scope) {
        var text = input.substring(0, 1).toUpperCase() + input.substring(1).toLowerCase();
        return text;
    }
});

app.directive('capitalizeFirst', ['$filter', function ($filter) {
    return {
        require: 'ngModel',
        link: function (scope, element, attrs, controller) {
            controller.$parsers.push(function (value) {
                var transformedInput = $filter('capitalizeFirst')(value);
                if (transformedInput !== value) {
                    var el = element[0];
                    el.setSelectionRange(el.selectionStart, el.selectionEnd);
                    controller.$setViewValue(transformedInput);
                    controller.$render();
                }
                return transformedInput;
            });
        }
    };
}]);

Here is a fiddle

Yoggi
  • 572
  • 4
  • 11
3

To fix the cursor problem (from where Mark Rajcok's solution), you can store element[0].selectionStart at the beginning of your method, and then ensure to reset element[0].selectionStart and element[0].selectionEnd to the stored value before the return. This should capture your selection range in angular

3

Generate directive:

ng g directive capitalizeFirst

Update file capitalize-first.directive.ts:

import {Directive, ElementRef, HostListener} from '@angular/core';

@Directive({
  selector: '[appCapitalizeFirst]'
})
export class CapitalizeFirstDirective {

  constructor(private ref: ElementRef) {
  }

  @HostListener('input', ['$event'])
  onInput(event: any): void {
    if (event.target.value.length === 1) {
      const inputValue = event.target.value;
      this.ref.nativeElement.value = inputValue.charAt(0).toUpperCase() + inputValue.substring(1);
    }
  }

}

Usage:

  <input appCapitalizeFirst>

This code woks with Angular 11+

CmoiJulien
  • 659
  • 8
  • 6
2

Comment to Mark Rajcok solution: when using $setViewValue, you trigger the parsers and validators again. If you add a console.log statement at the beginning of your capitalize function, you'll see it printed twice.

I propose the following directive solution (where ngModel is optional):

.directive('capitalize', function() {
   return {
     restrict: 'A',
     require: '?ngModel',
     link: function(scope, element, attrs, ngModel) {
         var capitalize = function (inputValue) {
             return (inputValue || '').toUpperCase();
         }
         if(ngModel) {
             ngModel.$formatters.push(capitalize);
             ngModel._$setViewValue = ngModel.$setViewValue;
             ngModel.$setViewValue = function(val){
                 ngModel._$setViewValue(capitalize(val));
                 ngModel.$render();
             };
         }else {
             element.val(capitalize(element.val()));
             element.on("keypress keyup", function(){
                 scope.$evalAsync(function(){
                     element.val(capitalize(element.val()));
                 });
             });
         }
     }
   };
});
marc carreras
  • 709
  • 5
  • 5
1

Here's a codepen for a filter that capitalizes the first letter: http://codepen.io/WinterJoey/pen/sfFaK

angular.module('CustomFilter', []).
  filter('capitalize', function() {
    return function(input, all) {
      return (!!input) ? input.replace(/([^\W_]+[^\s-]*) */g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}) : '';
    }
  });
Federico
  • 6,388
  • 6
  • 35
  • 43
1

Further to the CSS-only answers, you could always use Twitter Bootstrap:

<td class="text-capitalize">
dmvianna
  • 15,088
  • 18
  • 77
  • 106
0

Building off Mark Rajcok's solution; It's important to consider that the directive evaluate only when the input field in engaged, otherwise you'll get error messages firing off until the input field has a 1st character. Easy fix with a few conditionals: A jsfiddle to go with that: https://jsfiddle.net/Ely_Liberov/Lze14z4g/2/

      .directive('capitalizeFirst', function(uppercaseFilter, $parse) {
      return {
        require: 'ngModel',
        link: function(scope, element, attrs, modelCtrl) {
            var capitalize = function(inputValue) {
              if (inputValue != null) {
              var capitalized = inputValue.charAt(0).toUpperCase() +
                inputValue.substring(1);
              if (capitalized !== inputValue) {
                 modelCtrl.$setViewValue(capitalized);
                 modelCtrl.$render();
              }
              return capitalized;
            }
          };
          var model = $parse(attrs.ngModel);
          modelCtrl.$parsers.push(capitalize);
          capitalize(model(scope));
        }
       };
    });
0

The problem with css-ony answers is that the angular model is not updated with the view. This is because css only applies styling after rendering.

The following directive updates the model AND remembers the cursors location

app.module.directive('myCapitalize', [ function () {
        'use strict';

    return {
        require: 'ngModel',
        restrict: "A",
        link: function (scope, elem, attrs, modelCtrl) {

            /* Watch the model value using a function */
            scope.$watch(function () {
                return modelCtrl.$modelValue;
            }, function (value) {

                /**
                 * Skip capitalize when:
                 * - the value is not defined.
                 * - the value is already capitalized.
                 */
                if (!isDefined(value) || isUpperCase(value)) {
                    return;
                }

                /* Save selection position */
                var start = elem[0].selectionStart;
                var end = elem[0].selectionEnd;

                /* uppercase the value */
                value = value.toUpperCase();

                /* set the new value in the modelControl */
                modelCtrl.$setViewValue(value);

                /* update the view */
                modelCtrl.$render();

                /* Reset the position of the cursor */
                elem[0].setSelectionRange(start, end);
            });

            /**
             * Check if the string is defined, not null (in case of java object usage) and has a length.
             * @param str {string} The string to check
             * @return {boolean} <code>true</code> when the string is defined
             */
            function isDefined(str) {
                return angular.isDefined(str) && str !== null && str.length > 0;
            }

            /**
             * Check if a string is upper case
             * @param str {string} The string to check
             * @return {boolean} <code>true</code> when the string is upper case
             */
            function isUpperCase(str) {
                return str === str.toUpperCase();
            }
        }
    };
}]);
Marcel
  • 1,494
  • 1
  • 23
  • 33
  • This makes entire text capital. Am looking for only first letter to be capital. And cursor to be remained in its position – AkRoy Jun 19 '18 at 11:08
-1

You can use the provided uppercase filter.

http://docs.angularjs.org/api/ng.filter:uppercase

Gregor
  • 756
  • 4
  • 13
-3

You could use pure css:

input { text-transform: capitalize; }

JSHowTo
  • 98
  • 7
  • 3
    The OP only wants to capitalize the first letter in the input. This solution capitalizes each word – Chris Bier Oct 08 '13 at 16:39
  • @ChrisBier, I think you're confusing capitalize with uppercase. https://css-tricks.com/almanac/properties/t/text-transform/ – Mattias Sep 13 '20 at 08:44