Creating a symbol toolbar with Angular I have been given a requirement to create a symbol toolbar, a toolbar where each button represents a unique symbol. 

This is how the toolbar would work:


A user clicks inside a text field and enters data.
The user would then click a symbol on the toolbar and it will be appended to that specific text field.
The browser will put focus back on that text field.


I have created this toolbar, but I really dislike my implementation because it feels really hacky. In my implementation, I created two functions, addSymbol and setLastFocused in my controller:

$scope.addSymbol = function (field)
{
    if (!$scope.product[$scope.lastFocusedModelKey])
    {
        $scope.product[$scope.lastFocusedModelKey] = "";
    }

    $scope.product[$scope.lastFocusedModelKey] = $scope.product[$scope.lastFocusedModelKey] + field.symbol.SymbolCharacter;

    $('#' + $scope.lastFocusedModelKey).focus();
}

$scope.setLastFocused = function (lastFocusedModelKey)
{
    $scope.lastFocusedModelKey = lastFocusedModelKey;
}


SetLastFocused is called every time a user puts focus on a text field. I pass in a string of the field name like this:

&lt;input type="text" id="Foo" name="FooName" ng-model="product.Foo" class="form-control input-xs" ng-focus="setLastFocused('Foo')"&gt;


AddSymbol accepts the button field as a parameter, and appends the symbol to the model that is bound to the text field, and then re-focuses on the text field.

I feel like this is hacky because:


I have to use jQuery to re-focus on the text field.
I'm passing in a hardcoded string representing the field name (meaning if I change the name of the field and forget to change the hardcoded string, then this symbol feature will break for that specific text field).


This works fine now with a few text fields in which the users may want to append symbols, but now I'm learning I need to add the functionality to append symbols to text fields inside a whole Angular Ui-Grid (which exists inside a separate controller). So I am realizing I need to refactor this if I want this to work with a Ui-Grid as well. I know I need to move this code to a separate service, but I would also like to make this cleaner. I'm just not really sure how. 

How can I make this code follow best Angular practices?
With custom directives you could save the focus in a $scope.focusTarget variable and the resetting of the focus can be done with a focus broadcast that have the instanceId (=focusTarget e.g. 1, 2, ...) and the new icon class so each directive called iconInput will receive the broadcast and check if the passed ID is matching. If it matches it applies the focus and sets the new icon class.

The ID is generated with a counter that is incremented at every controller run of the iconInput directive.

Please have a look at the code below (then it's more clear how it works, it's a bit difficult to describe) or in this jsfiddle.

The first focus is only working in jsfiddle if you open the fiddle in full screen mode. Not sure what's wrong.



angular.module('demoApp', [])
    .directive('iconbar', IconbarDirective)
    .directive('iconInput', IconInputDirective)
 .factory('focus', FocusFactory)
    .controller('mainController', MainController);

function IconbarDirective(focus) {
    return {
        restrict: 'EA',
        transclude: true,
        scope: {
            focusTarget: '@'
        },
        template: '&lt;div class="iconbar" ng-transclude&gt;&lt;/div&gt;',
        link: function (scope, element) {
            var icons = element.find('i');
            console.log(icons);
            icons.addClass('iconbar-icon');
            element.find('i').bind('click', function (evt) {
                console.log('clicked', evt.target, this);
                var icon = angular.element(this),
                    iconClasses = icon[0].className.split('ng-scope'), // third is ng-scope
                 target = scope.focusTarget;

                //console.log(iconClasses);
                //console.log(iconClasses[0]);
                focus('focus' + target, iconClasses[0]);
                scope.$apply();
            });
        }
    }
}

function IconInputDirective() {
    var instanceCounter = 0;
    return {
        restrict: 'EA',
        scope: {
            target: '='
        },
        template: '&lt;div class="icon-input"&gt;&lt;i class="icon-placeholder" ng-class="placeholder()"&gt;&lt;/i&gt;' +
            '&lt;input type="text" placeholder="enter text..."/&gt;' +
            '&lt;/div&gt;',
        controller: function ($scope) {
            instanceCounter++;
            this.instanceId = instanceCounter;
            console.log('instance no.', instanceCounter);
            $scope.placeholder = function() {
             return $scope.icon || 'fa fa-bars icon-invisible';
            };
        },
        link: function (scope, element, attr, ctrl) {
            var inputBox = element.find('input');
            inputBox.bind('focus', function () {
                // clear other acitve classes
                element.parent().find('input').removeClass('active');
                inputBox.addClass('active');
                scope.target = ctrl.instanceId;
                scope.$apply();
            })
            /*   .bind('blur', function () {
                inputBox.removeClass('active');
            });*/
   
            // code for focusOn from here http://stackoverflow.com/questions/14833326/how-to-set-focus-on-input-field
            scope.$on('focusOn', function (e, name, iconClass) {
                console.log('focuson triggered', name, element[0]);
                if (name === 'focus' + ctrl.instanceId) {
                    console.log('focus now!!', element.find('input')[0]);
                    element.find('input')[0].focus();
                    inputBox.addClass('active');
                    scope.icon = iconClass;
                }
            });
        }
    }
}

function MainController($scope, focus) {
    $scope.focusTarget = 1; // last acitve input box --&gt; sets first focus
    focus('focus' + $scope.focusTarget); // activate focus with-out class
}

function FocusFactory($rootScope, $timeout) {
    return function (name, iconClass) {
        $timeout(function () {
            $rootScope.$broadcast('focusOn', name, iconClass);
        });
    }
}
.iconbar-icon {
  cursor: pointer;
}
.icon-placeholder {
  width: 16px;
  padding: 5px;
}
[iconbar] {
  border: 1px solid;
  padding: 5px;
  margin-bottom: 5px;
}
.active {
  border: 2px solid blue;
}
i {
  padding-right: 5px;
}
.icon-invisible {
  visibility: hidden;
}
/* spacer not working */

.icon-spacer::before,
.icon-blank::before {
  width: 1em;
  content: ' ';
}
&lt;link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.css" rel="stylesheet" /&gt;
&lt;script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"&gt;&lt;/script&gt;
&lt;div ng-app="demoApp" ng-controller="mainController"&gt;
  &lt;div iconbar focus-target="{{focusTarget}}"&gt; &lt;i class="fa fa-bars"&gt;&lt;/i&gt;
    &lt;i class="fa fa-bus"&gt;&lt;/i&gt;
    &lt;i class="fa fa-bug"&gt;&lt;/i&gt;
    &lt;i class="fa fa-beer"&gt;&lt;/i&gt;

  &lt;/div&gt;
  &lt;!--{{focusTarget}}--&gt;
  &lt;icon-input target="focusTarget"&gt;&lt;/icon-input&gt;
  &lt;icon-input target="focusTarget"&gt;&lt;/icon-input&gt;
&lt;/div&gt;