AngularJs parse example Directive

To illustrate this AngularJs parse example Directive in this example we are going to create a directive that allows you to create editable elements. Such technique is also known as click-to-edit or edit-in-place.

First, when should I use Directives in AngularJs?
Any time you need to alter the DOM, Insert, Delete, Modify a node, add an event handler, etc.
If you are using JQuery within a controller or within a Service you’re doing it wrong, That logic needs to be moved to a Directive.

AngularJs parse example

The directives enable the re-utilization of the code (applying the DRY principle).

Example:

This directive, what it does is, it substitute the text inside a container for an Html input text (the text become editable). That’s why I called this Directive “edit-in-place”:

 .directive('editInPlace',['$parse', '$timeout', function ($parse, $timeout) {
            return {
                scope: {
                    editInPlace: '=editInPlace',
                    onDone: '&onDone'
                },
                link: function (scope, element, attrs) {

                    element.parent().append('<span class="edit-in-place-span" >' + scope.editInPlace + ' </span>');
                    var $editSpan = element.parent().find('span.edit-in-place-span');

                    //we are using bootstrap icon-ok for the Done button
                    element.parent().append('<span class="edit-in-place-edit-wrapper">
                    <input type="text" class="edit-input" value="' + scope.editInPlace + '" />
                    <i class="icon-ok edit-in-place-done"></i></span>');
                    var $editInput = element.parent().find('.edit-in-place-edit-wrapper');
                    $editInput.hide();

                    element.parent().find('span.edit-in-place-span').click(function (e) {
                        if ($editInput.is(":visible")) {
                            //read only mode
                            $editInput.hide();
                            $editSpan.show();
                        } else {
                            //edit mode
                            $editInput.show();
                            $editInput.find('input').focus().select();
                            $editSpan.hide();
                        }

                    });

                    element.parent().find('i.edit-in-place-done').click(function (e) {
                        scope.editInPlace = element.parent().find('input.edit-input').val();

                        $timeout(function() {
                            //this is to be able to assign the changes back to the source
                            var parsed = $parse(attrs.editInPlace);
                            //used scope.$parent
                            parsed.assign(scope.$parent, scope.editInPlace);
                        });

                        $editSpan.text(scope.editInPlace);
                        $editInput.hide();
                        $editSpan.show();
                        if (typeof scope.onDone == "function") {
                            scope.onDone(); //this is the callback called when finished to edit
                        }
                    });

                },
            }
        }]);

Let’s analyze the code:

Notice the way we inject the dependencies:

        .directive('editInPlace', ['$parse', '$timeout', function ($parse, $timeout) {

This is a best practice that will helps you to avoid errors when you minify/compress the code, because the anonymous function parameters become something like this:

function(a,b){} so when you reference in your code $parse or $timeout it will throw an error. That’s why Angular provides the option to pass it as string.

In the isolate scope we have two variables:

   scope: {
      editInPlace: '=editInPlace',
      onDone: '&onDone'
          },

We are explicitly expecting the scope.editingInPlace variable as a value (notice the = symbol): ‘=editInPlace’, and onDone as a function (notice the & symbol).

editingInPlace will be the value we going to modify.
and onDone will be the callback function we going to call when finished editing.

We are going to use the $parse service to parse the attrs.editInPlace attribute and be able to modify the scope variable.

With the $parse service we can convert Angular expression into a function/objects, in this case we going to convert scope.editInPlace expression into an object

  //note: this code is a getter
  var parsed = $parse(attrs.editInPlace)(scope);

  //and this code is a setter
  //this is to be able to assign the changes back to the source
  var parsed = $parse(attrs.editInPlace);
  //used scope.$parent
  parsed.assign(scope.$parent, scope.editInPlace);

 

 

book

Book Tip

ng-book – The Complete Book on AngularJS
Ready to master AngularJS? What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall?


Amazon

 

Here is a working example:

 

This directive works perfect. Now, this doesn’t mean that this is the best approach used for this AngularJs parse example (we will still using the parse service for this example).

sphagetti-code directive AngularJs parse example

In this specific example the directive uses a lot of JQuery code, winch is not a good practice at all, specially when you can achieve the same result using the “angular way”.
This should be the result of rewrite the directive to the angular way:

myApp.directive("editInPlace", function() {
        return {
            restrict: 'A',
            scope: true,
            template: [
                '
<div ng-switch="edit.editing">',
                    '
<div ng-switch-when="true">',
                        '<input type="text" ng-model="edit.value" />',
                        '<button ng-click="edit.toggleEdit()">Done</button>',
                    '</div>

',
                    '<span ng-switch-default ng-click="edit.toggleEdit()">{{ edit.value }}</span>',
                '</div>

'
            ].join(""),
            controller: function($scope, $attrs, $parse) {
                var edit = this;
                edit.editing = false;
                edit.value = $parse($attrs.editInPlace)($scope);
                edit.toggleEdit = function() {
                    debugger
                    edit.editing = !edit.editing;
                    
                    //you can use either $scope.$eval or $parse to update de object 
                    //$scope.$eval([$attrs.editInPlace , "=", JSON.stringify(edit.value)].join(""));
                    $parse($attrs.editInPlace).assign($scope, edit.value);
                };
            },
            controllerAs: "edit"
        };
    });

 

You can try the full working example of the parse here:

This directive was tested with AngularJs version 1.3.3

If you have any question about this AngularJs parse example, do not hesitate to ask in the comments of this blog, or on twitter. 😉