Cela fait maintenant deux ans et demi que je travaille avec AngularJS comme framework pour tous mes projets front. Au fil des projets, on amasse de plus en plus d’expériences et certains bouts de codes deviennent indispensables. Voici donc le premier article d’une série d’outils (directives, configs…) que j’intègre à presque tous mes projets AngularJS.

AngularJS gère très bien le “binding” des éléments input, select et textarea grâce à la directive ngModel. En revanche, pour l’attribut contenteditable, rien n’est prévu.

Voici donc la directive cg-contenteditable qui permet de combler ce manque (c’est en CoffeeScript mais vous pouvez voir la version compilée dans le CodePen à la fin de cet article) :

L’idée de cette directive est de gérer l’attribut contenteditable de la même façon qu’Angular gère les éléments ciblés par ngModel. Pour être cohérent au niveau du DOM on va donc retrouver l’attribut ng-model qui nous servira à gérer le “double binding” des données.

Cette directive se divise en trois parties. Dans un premier temps : (1) l’initialisation; on ajoute l’attribut contenteditable à l’élément. Ensuite, (2) on va écouter l’événement input afin de mettre à jour le model quand on tape dans l’élément. À noter l’utilisation de scope.$evalAsync() qui va déclancher un “$digest cycle” pour véritablement mettre à jour l’objet représenté par ngModel. Enfin (3) on regarde les changements sur le ngModel avec un $watch pour les répercuter sur l’élément.

Pour finir, voici un petit CodePen qui montre comment on l’utilise :

angular.module('whatever', {})

angular.module('whatever').controller 'test', ($scope) ->
    $scope.item =
        description: null

    $scope.$watch 'item.description', ->
        console.log $scope.item

    $scope.item.description = 'test'

angular.module('whatever').directive 'cgContenteditable', ->

    restrict: 'A'
    scope:
        ngModel: '='
    link: (scope, elem, attrs) ->
        elem = elem[0]
        elem.setAttribute 'contenteditable', true

        elem.addEventListener 'input', ->
            return if scope.ngModel is elem.innerHTML
            scope.ngModel = elem.innerHTML
            scope.$evalAsync()

        scope.$watch 'ngModel', ->
            return unless scope.ngModel
            return if scope.ngModel is elem.innerHTML
            elem.innerHTML = scope.ngModel

See the Pen QbZVgW by roparz (@roparz) on CodePen.