2

I'm working on a project to render HTML taking a special XML as input. I succeed already with the basic case. For example:

<mybutton name="layla"></mybutton>

is converted to

<div class="btn">Layla</div>

using a directive like

.directive('mybutton', function() {
    return {
        restrict: 'E',
        scope: {
            name: '@'
        },
        templateUrl: 'uicomponents/uiButton.html'
    }
})

The real XML input I'll receive is:

<ui type="back_button" x="10.0" y="630.0"/>

I'd like to avoid changing the XML syntax but it can be done if necessary. all screen components are in <ui></ui> tags. The type attribute defines the component.

How would you write directives for this kind of tags? It doesn't seem smart to create one huge directive for <ui> elements and having a long switch-case inside for the attribute.

Eduard Gamonal
  • 8,023
  • 5
  • 41
  • 46

2 Answers2

5

You could create a ui directive that transforms the element based on the type attribute and then recompiles the element - something like this:

app.directive('ui',function($compile){
  return {
    restrict:'E',
    compile:function(tElement,tAttrs){
      if(tAttrs.type){
        tElement.attr(tAttrs.type,'');
        tElement.removeAttr('type');
        return function(scope,el){
          $compile(el)(scope);
        }
      }

    }  
  }
});


app.directive('backButton',function(){
  return {
    replace:true,
    template:'<button ng-click="btnClick()">A back-button template</button>'
  }
});

Edit: In my original example I made the mistake of compiling the template element - this will not work if the directive is being used in a ngRepeat. The fix is simple, compile the link element instead. I've updated the code and the example.

Check out this plnkr example.

joakimbl
  • 18,081
  • 5
  • 54
  • 53
  • Nice answer. I tried a similar thing, but instead of using a compile function it used a link function to add a the type as a CSS class that was supposed to trigger the second directive. Any idea why it never runs the second directive? [fiddle](http://jsfiddle.net/yxFrN/) – mfelix Apr 10 '13 at 20:07
  • Hard to say - did you remember to include the [$compile](http://docs.angularjs.org/api/ng.$compile) service and recompile the element after you added CSS class? You can read more about the compile process [here](http://docs.angularjs.org/guide/directive). If you could create an example at [plnkr](http://plnkr.co) I could have a look – joakimbl Apr 10 '13 at 21:35
  • I put a fiddle link in my comment above; maybe you didn't see it? I'll read up on $compile, thanks. – mfelix Apr 10 '13 at 22:39
  • Sorry, missed that! You're missing the $compile service, so your updated element won't get compiled. And you should do the element manipulation in the compile function - [updated fiddle](http://jsfiddle.net/yxFrN/1/) – joakimbl Apr 11 '13 at 05:46
  • Hi, awesome! you helped me understand much better the compile function. Just for the record, there was a small mistake in the code. the directive for backButton should be backbutton (lowercase). http://jsfiddle.net/WptGC/ – Eduard Gamonal Apr 11 '13 at 07:34
  • 1
    hmm, there seems to be a problem if you put this in a ng-repeat: http://plnkr.co/edit/ebSOSXIf9qartMK76eJA?p=preview –  Apr 24 '13 at 10:44
  • I added `scope: true` in the ui directive. otherwise, if two `` siblings share the scope. – Eduard Gamonal Apr 25 '13 at 11:12
2

I am not aware of a way of creating a directive bound on the value of an attribute, but maybe you could have a look at this previous SO answer: Custom Input Types

The idea is to create a different template according to the value of an attribute using the compile function of the directive. You could put in cache all your custom input templates at the beginning (not necessary).

angular.module('myapp', ['myapp.directives'])
    .run(['$http', '$templateCache', function($http, $templateCache) {
        $templateCache.put('span.html', '<span>text</span>');
        $http.get('back_button.html', {cache:$templateCache});
    }]);

And retrieve from the cache the input template according to the type attribute:

angular.module('myapp.directives', [])
    .directive('ui', ['$templateCache', function($templateCache) {
        return {
            restrict: 'E',
            compile: function(elem, attrs)
            {
                var type = attrs.type || 'span.html'; // default value ?
                elem.replaceWith($templateCache.get(type));
            }
        }
    }]);

This code wasn't tested.

EDIT: This could also work, i think, using the link function with $compile, without preloading the template, but still caching them:

angular.module('myapp.directives', [])
    .directive('ui', ['$http', '$templateCache', '$compile', function($http, $templateCache, $compile) {
        return {
            restrict: 'E',
            link: function(scope, elem, attrs)
            {
                var type = attrs.type || 'span.html'; // default value ?
                $http.get('views/' + type + '.html', {cache: $templateCache}).then(function(result) {
                    elem.replaceWith($compile(result.data)(scope));
                });
            }
        }
    }]);

This code worked for me.

Community
  • 1
  • 1
jpmorin
  • 6,008
  • 2
  • 28
  • 39
  • This is a really good idea. I started converting `` (initial input) to `` (one directive per type), which is eventually converted to `
    content
    ` (html for the browser). Templates coming from the legacy system are files that seem good for `templateUrl` but I was concerned about the performance of loading them dynamically instead of just hardcoding them in the link/compile functions. Your post shows how to use the cache in a clear way. Thanks!
    – Eduard Gamonal Jul 23 '13 at 12:26