function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture) {
  return {
    restrict: 'E',
    require: ['^?mdInputContainer', '?ngModel', '?^form'],
    link: postLink
  };

  function postLink(scope, element, attr, ctrls) {

    var containerCtrl = ctrls[0];
    var hasNgModel = !!ctrls[1];
    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();
    var parentForm = ctrls[2];
    var isReadonly = angular.isDefined(attr.readonly);
    var mdNoAsterisk = $mdUtil.parseAttributeBoolean(attr.mdNoAsterisk);
    var tagName = element[0].tagName.toLowerCase();


    if (!containerCtrl) return;
    if (attr.type === 'hidden') {
      element.attr('aria-hidden', 'true');
      return;
    } else if (containerCtrl.input) {
      if (containerCtrl.input[0].contains(element[0])) {
        return;
      } else {
        throw new Error("<md-input-container> can only have *one* <input>, <textarea> or <md-select> child element!");
      }
    }
    containerCtrl.input = element;

    setupAttributeWatchers();

    // Add an error spacer div after our input to provide space for the char counter and any ng-messages
    var errorsSpacer = angular.element('<div class="md-errors-spacer">');
    element.after(errorsSpacer);

    var placeholderText = angular.isString(attr.placeholder) ? attr.placeholder.trim() : '';
    if (!containerCtrl.label && !placeholderText.length) {
      $mdAria.expect(element, 'aria-label');
    }

    element.addClass('md-input');
    if (!element.attr('id')) {
      element.attr('id', 'input_' + $mdUtil.nextUid());
    }

    // This works around a Webkit issue where number inputs, placed in a flexbox, that have
    // a `min` and `max` will collapse to about 1/3 of their proper width. Please check #7349
    // for more info. Also note that we don't override the `step` if the user has specified it,
    // in order to prevent some unexpected behaviour.
    if (tagName === 'input' && attr.type === 'number' && attr.min && attr.max && !attr.step) {
      element.attr('step', 'any');
    } else if (tagName === 'textarea') {
      setupTextarea();
    }

    // If the input doesn't have an ngModel, it may have a static value. For that case,
    // we have to do one initial check to determine if the container should be in the
    // "has a value" state.
    if (!hasNgModel) {
      inputCheckValue();
    }

    var isErrorGetter = containerCtrl.isErrorGetter || function() {
      return ngModelCtrl.$invalid && (ngModelCtrl.$touched || (parentForm && parentForm.$submitted));
    };

    scope.$watch(isErrorGetter, containerCtrl.setInvalid);

    // When the developer uses the ngValue directive for the input, we have to observe the attribute, because
    // AngularJS's ngValue directive is just setting the `value` attribute.
    if (attr.ngValue) {
      attr.$observe('value', inputCheckValue);
    }

    ngModelCtrl.$parsers.push(ngModelPipelineCheckValue);
    ngModelCtrl.$formatters.push(ngModelPipelineCheckValue);

    element.on('input', inputCheckValue);

    if (!isReadonly) {
      element
        .on('focus', function(ev) {
          $mdUtil.nextTick(function() {
            containerCtrl.setFocused(true);
          });
        })
        .on('blur', function(ev) {
          $mdUtil.nextTick(function() {
            containerCtrl.setFocused(false);
            inputCheckValue();
          });
        });
    }

    scope.$on('$destroy', function() {
      containerCtrl.setFocused(false);
      containerCtrl.setHasValue(false);
      containerCtrl.input = null;
    });

    /** Gets run through ngModel's pipeline and set the `has-value` class on the container. */
    function ngModelPipelineCheckValue(arg) {
      containerCtrl.setHasValue(!ngModelCtrl.$isEmpty(arg));
      return arg;
    }

    function setupAttributeWatchers() {
      if (containerCtrl.label) {
        attr.$observe('required', function (value) {
          // We don't need to parse the required value, it's always a boolean because of AngularJS'
          // required directive.
          if (containerCtrl.label) {
            containerCtrl.label.toggleClass('md-required', value && !mdNoAsterisk);
          }
        });
      }
    }

    function inputCheckValue() {
      // An input's value counts if its length > 0,
      // or if the input's validity state says it has bad input (eg string in a number input)
      containerCtrl.setHasValue(element.val().length > 0 || (element[0].validity || {}).badInput);
    }

    function setupTextarea() {
      var isAutogrowing = !attr.hasOwnProperty('mdNoAutogrow');

      attachResizeHandle();

      if (!isAutogrowing) return;

      // Can't check if height was or not explicity set,
      // so rows attribute will take precedence if present
      var minRows = attr.hasOwnProperty('rows') ? parseInt(attr.rows) : NaN;
      var maxRows = attr.hasOwnProperty('maxRows') ? parseInt(attr.maxRows) : NaN;
      var scopeResizeListener = scope.$on('md-resize-textarea', growTextarea);
      var lineHeight = null;
      var node = element[0];

      // This timeout is necessary, because the browser needs a little bit
      // of time to calculate the `clientHeight` and `scrollHeight`.
      $timeout(function() {
        $mdUtil.nextTick(growTextarea);
      }, 10, false);

      // We could leverage ngModel's $parsers here, however it
      // isn't reliable, because AngularJS trims the input by default,
      // which means that growTextarea won't fire when newlines and
      // spaces are added.
      element.on('input', growTextarea);

      // We should still use the $formatters, because they fire when
      // the value was changed from outside the textarea.
      if (hasNgModel) {
        ngModelCtrl.$formatters.push(formattersListener);
      }

      if (!minRows) {
        element.attr('rows', 1);
      }

      angular.element($window).on('resize', growTextarea);
      scope.$on('$destroy', disableAutogrow);

      function growTextarea() {
        // temporarily disables element's flex so its height 'runs free'
        element
          .attr('rows', 1)
          .css('height', 'auto')
          .addClass('md-no-flex');

        var height = getHeight();

        if (!lineHeight) {
          // offsetHeight includes padding which can throw off our value
          var originalPadding = element[0].style.padding || '';
          lineHeight = element.css('padding', 0).prop('offsetHeight');
          element[0].style.padding = originalPadding;
        }

        if (minRows && lineHeight) {
          height = Math.max(height, lineHeight * minRows);
        }

        if (maxRows && lineHeight) {
          var maxHeight = lineHeight * maxRows;

          if (maxHeight < height) {
            element.attr('md-no-autogrow', '');
            height = maxHeight;
          } else {
            element.removeAttr('md-no-autogrow');
          }
        }

        if (lineHeight) {
          element.attr('rows', Math.round(height / lineHeight));
        }

        element
          .css('height', height + 'px')
          .removeClass('md-no-flex');
      }

      function getHeight() {
        var offsetHeight = node.offsetHeight;
        var line = node.scrollHeight - offsetHeight;
        return offsetHeight + Math.max(line, 0);
      }

      function formattersListener(value) {
        $mdUtil.nextTick(growTextarea);
        return value;
      }

      function disableAutogrow() {
        if (!isAutogrowing) return;

        isAutogrowing = false;
        angular.element($window).off('resize', growTextarea);
        scopeResizeListener && scopeResizeListener();
        element
          .attr('md-no-autogrow', '')
          .off('input', growTextarea);

        if (hasNgModel) {
          var listenerIndex = ngModelCtrl.$formatters.indexOf(formattersListener);

          if (listenerIndex > -1) {
            ngModelCtrl.$formatters.splice(listenerIndex, 1);
          }
        }
      }

      function attachResizeHandle() {
        if (attr.hasOwnProperty('mdNoResize')) return;

        var handle = angular.element('<div class="md-resize-handle"></div>');
        var isDragging = false;
        var dragStart = null;
        var startHeight = 0;
        var container = containerCtrl.element;
        var dragGestureHandler = $mdGesture.register(handle, 'drag', { horizontal: false });


        element.wrap('<div class="md-resize-wrapper">').after(handle);
        handle.on('mousedown', onMouseDown);

        container
          .on('$md.dragstart', onDragStart)
          .on('$md.drag', onDrag)
          .on('$md.dragend', onDragEnd);

        scope.$on('$destroy', function() {
          handle
            .off('mousedown', onMouseDown)
            .remove();

          container
            .off('$md.dragstart', onDragStart)
            .off('$md.drag', onDrag)
            .off('$md.dragend', onDragEnd);

          dragGestureHandler();
          handle = null;
          container = null;
          dragGestureHandler = null;
        });

        function onMouseDown(ev) {
          ev.preventDefault();
          isDragging = true;
          dragStart = ev.clientY;
          startHeight = parseFloat(element.css('height')) || element.prop('offsetHeight');
        }

        function onDragStart(ev) {
          if (!isDragging) return;
          ev.preventDefault();
          disableAutogrow();
          container.addClass('md-input-resized');
        }

        function onDrag(ev) {
          if (!isDragging) return;

          element.css('height', (startHeight + ev.pointer.distanceY) + 'px');
        }

        function onDragEnd(ev) {
          if (!isDragging) return;
          isDragging = false;
          container.removeClass('md-input-resized');
        }
      }

      // Attach a watcher to detect when the textarea gets shown.
      if (attr.hasOwnProperty('mdDetectHidden')) {

        var handleHiddenChange = function() {
          var wasHidden = false;

          return function() {
            var isHidden = node.offsetHeight === 0;

            if (isHidden === false && wasHidden === true) {
              growTextarea();
            }

            wasHidden = isHidden;
          };
        }();

        // Check every digest cycle whether the visibility of the textarea has changed.
        // Queue up to run after the digest cycle is complete.
        scope.$watch(function() {
          $mdUtil.nextTick(handleHiddenChange, false);
          return true;
        });
      }
    }
  }
}
@ngdoc directive
@name mdInput
@restrict E
@module material.components.input

@description
You can use any `<input>` or `<textarea>` element as a child of an `<md-input-container>`. This
allows you to build complex forms for data entry.

When the input is required and uses a floating label, then the label will automatically contain
an asterisk (`*`).<br/>
This behavior can be disabled by using the `md-no-asterisk` attribute.

@param {number=} md-maxlength The maximum number of characters allowed in this input. If this is
  specified, a character counter will be shown underneath the input.<br/><br/>
  The purpose of **`md-maxlength`** is exactly to show the max length counter text. If you don't
  want the counter text and only need "plain" validation, you can use the "simple" `ng-maxlength`
  or maxlength attributes.<br/><br/>
@param {boolean=} ng-trim If set to false, the input text will be not trimmed automatically.
    Defaults to true.
@param {string=} aria-label Aria-label is required when no label is present.  A warning message
  will be logged in the console if not present.
@param {string=} placeholder An alternative approach to using aria-label when the label is not
  PRESENT. The placeholder text is copied to the aria-label attribute.
@param {boolean=} md-no-autogrow When present, textareas will not grow automatically.
@param {boolean=} md-no-asterisk When present, an asterisk will not be appended to the inputs
  floating label.
@param {boolean=} md-no-resize Disables the textarea resize handle.
@param {number=} max-rows The maximum amount of rows for a textarea.
@param {boolean=} md-detect-hidden When present, textareas will be sized properly when they are
  revealed after being hidden. This is off by default for performance reasons because it
  guarantees a reflow every digest cycle.

@usage
<hljs lang="html">
<md-input-container>
  <label>Color</label>
  <input type="text" ng-model="color" required md-maxlength="10">
</md-input-container>
</hljs>

<h3>With Errors</h3>

`md-input-container` also supports errors using the standard `ng-messages` directives and
animates the messages when they become visible using from the `ngEnter`/`ngLeave` events or
the `ngShow`/`ngHide` events.

By default, the messages will be hidden until the input is in an error state. This is based off
of the `md-is-error` expression of the `md-input-container`. This gives the user a chance to
fill out the form before the errors become visible.

<hljs lang="html">
<form name="colorForm">
  <md-input-container>
    <label>Favorite Color</label>
    <input name="favoriteColor" ng-model="favoriteColor" required>
    <div ng-messages="colorForm.favoriteColor.$error">
      <div ng-message="required">This is required!</div>
    </div>
  </md-input-container>
</form>
</hljs>

We automatically disable this auto-hiding functionality if you provide any of the following
visibility directives on the `ng-messages` container:

 - `ng-if`
 - `ng-show`/`ng-hide`
 - `ng-switch-when`/`ng-switch-default`

You can also disable this functionality manually by adding the `md-auto-hide="false"` expression
to the `ng-messages` container. This may be helpful if you always want to see the error messages
or if you are building your own visibility directive.

_<b>Note:</b> The `md-auto-hide` attribute is a static string that is  only checked upon
initialization of the `ng-messages` directive to see if it equals the string `false`._

<hljs lang="html">
<form name="userForm">
  <md-input-container>
    <label>Last Name</label>
    <input name="lastName" ng-model="lastName" required md-maxlength="10" minlength="4">
    <div ng-messages="userForm.lastName.$error" ng-show="userForm.lastName.$dirty">
      <div ng-message="required">This is required!</div>
      <div ng-message="md-maxlength">That's too long!</div>
      <div ng-message="minlength">That's too short!</div>
    </div>
  </md-input-container>
  <md-input-container>
    <label>Biography</label>
    <textarea name="bio" ng-model="biography" required md-maxlength="150"></textarea>
    <div ng-messages="userForm.bio.$error" ng-show="userForm.bio.$dirty">
      <div ng-message="required">This is required!</div>
      <div ng-message="md-maxlength">That's too long!</div>
    </div>
  </md-input-container>
  <md-input-container>
    <input aria-label='title' ng-model='title'>
  </md-input-container>
  <md-input-container>
    <input placeholder='title' ng-model='title'>
  </md-input-container>
</form>
</hljs>

<h3>Notes</h3>

- Requires [ngMessages](https://docs.angularjs.org/api/ngMessages).
- Behaves like the [AngularJS input directive](https://docs.angularjs.org/api/ng/directive/input).

The `md-input` and `md-input-container` directives use very specific positioning to achieve the
error animation effects. Therefore, it is *not* advised to use the Layout system inside of the
`<md-input-container>` tags. Instead, use relative or absolute positioning.


<h3>Textarea directive</h3>
The `textarea` element within a `md-input-container` has the following specific behavior:
- By default the `textarea` grows as the user types. This can be disabled via the `md-no-autogrow`
attribute.
- If a `textarea` has the `rows` attribute, it will treat the `rows` as the minimum height and will
continue growing as the user types. For example a textarea with `rows="3"` will be 3 lines of text
high initially. If no rows are specified, the directive defaults to 1.
- The textarea's height gets set on initialization, as well as while the user is typing. In certain situations
(e.g. while animating) the directive might have been initialized, before the element got it's final height. In
those cases, you can trigger a resize manually by broadcasting a `md-resize-textarea` event on the scope.
- If you want a `textarea` to stop growing at a certain point, you can specify the `max-rows` attribute.
- The textarea's bottom border acts as a handle which users can drag, in order to resize the element vertically.
Once the user has resized a `textarea`, the autogrowing functionality becomes disabled. If you don't want a
`textarea` to be resizeable by the user, you can add the `md-no-resize` attribute.