Edit in JSFiddle

(function(window, document) {
  'use strict';

  var angular = window.angular;
  var module = angular.module('App', ['mgcrea.ngStrap']);

  module.controller('InputCtrl', [
    '$scope',
    function($scope) {
    }
  ]);

  module.directive('validateMessage', [
  	'$tooltip',
    '$rootScope',
  	function($tooltip, $rootScope) {
    
    	return {

      	'restrict' : 'A',

        'scope' : {
          'errors'   : '=',
          'focused'  : '='
        },

        'link' : function(scope, element, attrs, ctrl) {
          var selector = '#' + attrs.id;
          var targetElement = angular.element(document.querySelector(selector));
          
         	var tooltipScope = $rootScope.$new();
          var tooltipParams = {
            'placement': 'right',
            'trigger'  : 'manual',
            'scope'    : tooltipScope
          };
          
          var myTooltip = $tooltip(targetElement, tooltipParams);
          
        	scope.$watch('errors', function() {
            if (typeof scope.focused === 'undefined' || scope.focused === null) {
              _display();
            }
            else if (!scope.focused) {
              myTooltip.hide();
            }
            else {
              _display();
            }
          }, true);
          
          var _display = function() {
            if (_hasError()) {
              tooltipScope.title = _makeErrorMessage();
            	myTooltip.show();
            }
            else {
            	myTooltip.hide();
            }
          };
          
          var _hasError = function() {
            for (var key in scope.errors) {
            	return true;
            }
            return false;
          };
          
          var _makeErrorMessage = function() {
          	var text = '';
          	for (var key in scope.errors) {
            	if (key === 'required' && scope.errors[key]) {
                text += '必須項目です。 ';
              }
              else if (key === 'min' && scope.errors[key]) {
                text += '小さすぎます。 ';
              }
              else if (key === 'max' && scope.errors[key]) {
                text += '大きすぎます。 ';
              }
              else if (key === 'maxlength' && scope.errors[key]) {
                text += '長すぎます。 ';
              }
              else if (key === 'pattern' && scope.errors[key]) {
                text += ' (´◉◞౪◟◉) ';
              }
            }
            return text;
          };
        }
      };
    }
  ]);


})(window, document);
<div ng-app="App" ng-controller="InputCtrl">
  <form name="myform" novalidate>
    名前:<input type="text" id="test" name="name" ng-model="user.name"
          validate-message
          required
          maxlength="5"
          errors="myform.name.$error"
          focused="myform.name.$dirty">
          <span>必須項目。文字列長さ5文字</span>
    <br>
    年齢:<input type="number" id="age" name="age" ng-model="user.age"
          validate-message
          required
          min="5"
          max="100"
          maxlength="3"
          errors="myform.age.$error">
          <span>必須項目。範囲5~100。文字列長さ3文字</span>
    <br>
    <br>
    メールアドレス:<input type="text" id="email" name="email" ng-model="user.email"
          validate-message
          ng-pattern="/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/"
          maxlength="256"
          errors="myform.email.$error">
          <span>フォーマットが正しいメールアドレスじゃないとキモい顔文字表示</span>
    <br>
    <input type="submit" value="登録">
  </form>
</div>