<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
    <link href="style.css" rel="stylesheet" />
    <script src="script.js"></script>
  </head>

  <body ng-controller="ctrl">

    <div tag-suggest>
      
      <div id="suggest-search" class="inputor" contenteditable="true" strip-br="true" ng-model="suggestContent" suggesteditable></div>
      <div id="suggest-wrapper">
        <ul id="suggest">
          <li class="suggest-item" ng-click="select($event, person)" ng-repeat="person in people | filter:tagsearch">
            <img src="http://placehold.it/50x50" height="50px" />
            <a href=""><span>{{person.name}}</span></a>
          </li>
        </ul>
      </div>
      
    </div>
    
    <button ng-click="send()">Send</button>

    {{suggestContent}}
    
  </body>

</html>
// Code goes here

angular.module('app', [])
.controller('ctrl', function($scope){
  
  $scope.people = [
    {id: 1, name: 'A. Barros'},
    {id: 2, name: 'D. Piva'},
    {id: 3, name: 'C. Silva'},
    {id: 4, name: 'M. Romanosque'},
    {id: 5, name: 'R. Sousa'},
    {id: 6, name: 'G. Souza'},
    {id: 7, name: 'A. Silva'}
  ]
})
.directive('suggesteditable', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModel) {
        if(!ngModel) return; // do nothing if no ng-model

        // Specify how UI should be updated
        ngModel.$render = function() {
          element.html(ngModel.$viewValue || '');
        };

        // Listen for change events to enable binding
        element.on('blur keyup change', function() {
          scope.$apply(read);
        });
        read(); // initialize

        // Write data to the model
        function read() {
          var html = element.html();
          // When we clear the content editable the browser leaves a <br> behind
          // If strip-br attribute is provided then we strip this out
          if( attrs.stripBr && (html == '<br>' || html == '<br class="ng-scope">' )) {
            html = '';
          }
          ngModel.$setViewValue(html);
        }
      }
  };
})
.directive('tagSuggest', function($filter, $parse, $compile){
  function placeCaretAtEnd(el) {
      el.focus();
      if (typeof window.getSelection != "undefined"
              && typeof document.createRange != "undefined") {
          var range = document.createRange();
          range.selectNodeContents(el);
          range.collapse(false);
          var sel = window.getSelection();
          sel.removeAllRanges();
          sel.addRange(range);
      } else if (typeof document.body.createTextRange != "undefined") {
          var textRange = document.body.createTextRange();
          textRange.moveToElementText(el);
          textRange.collapse(false);
          textRange.select();
      }
  }
  
  return {
    restrict: 'A',
    controller: function($scope, $element) {
      
      var input = angular.element($element[0].children[0]);
      var word  =/@(\w+)/ig;  //@abc Match
      var suggestBox = angular.element($element[0].children[1]);
      
      $scope.select = function($event, item) {
        
        var old     = input.html();
        var replace = '<p class="item-container"><span contenteditable="false" class="item-id">' + item.id + '</span><span class="item-name" contenteditable="false"> ' + item.name + '</span> </p> ';
        var content = old.replace(word, replace); 
        
        input.html(content);
        
        suggestBox.css({display:'none'});
        
        placeCaretAtEnd(input[0]);
        
      }
      
    },
    link: function(scope,elm,attrs) {
      
      var input = angular.element(elm[0].children[0]);
      var suggestBox = angular.element(elm[0].children[1]);
      var start =/@/ig;       // @ Match 
      var word  =/@(\w+)/ig;  //@abc Match
      
      suggestBox.css({display:'none'});

      elm.bind("keyup", function(e) {
        
        var content = input.html();         //Content Box Data
        var go      = content.match(start); //Content Matching @
        var name    = content.match(word);  //Content Matching @abc

        if( go !== null && go.length > 0) {
          
          var search = (name === null) ? null : name[0].replace(go, '');
          $parse('tagsearch').assign(scope, search);
          
          scope.$apply();
          if (search !== null && search.length > 0) {
            suggestBox.css({display:'block'});
            
            
          } else {
            suggestBox.css({display:'none'});
          }
          
        } else {
          suggestBox.css({display:'none'});
        }
        
      });
      
    }
  }
})

#suggest-wrapper {
  position:absolute;
  width:100%;
  z-index: 10;
}

#suggest {
  list-style: none outside none;
  margin: auto;
  padding: 0;
}

.suggest-item {
  background: none repeat scroll 0 0 #EEEEEE;
  font-size: 14px;
  padding: 7px;
  position: relative;
  max-width: 250px;
  border-bottom: 1px solid #FFFFFF;
  cursor: pointer;
  display: block;
}

.suggest-item a:hover {
  color:#ccc;
}

.suggest-item span {
  left: 70px;
  position: absolute;
  top: 23px;
}

.item-container {display:inline;}

.item-id { display:none;}

.item-name{
  border:1px solid;
  background:#333;
  color:white;
  padding:0 2px;
  font-size:95%;
}
.item-delete {padding:0 6px; color:red; cursor:pointer;}

.a {color:  red;}

.inputor {
  background: none repeat scroll 0 0 #FFFFFF;
  border: 1px solid #DADADA;
  border-radius: 4px;
  font-size: inherit;
  min-height: 40px;
  margin: 10px 0 0;
  outline: 0 none;
  overflow-y: scroll;
  padding: 5px 8px;
  max-width: 90%;
}
http://ichord.github.io/At.js/