Skip to content

Instantly share code, notes, and snippets.

@machty
Created April 29, 2014 05:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save machty/11391346 to your computer and use it in GitHub Desktop.
Save machty/11391346 to your computer and use it in GitHub Desktop.
Angular question about mixing in directive behaviors

Let's say I've declared three class-restricted directives "am-wat", "am-foo", and "am-bar", that each augment the attached element with some behavior, e.g.:

<div am-wat="some data" am-foo="other data" am-bar="lol">
  Yadda yadda yadda
</div>

Is there a way to write a directive that mixes in the behavior of the three directives, e.g.:

<div am-trifecta="trifecta args">
  Yadda yadda yadda
</div>

such that the trifecta directive invokes the behavior of each of the three directives on that div, passing each directive some args that the trifecta directive came up with? Can it be done in without requiring an intermediate DOM element being added, which might complicated or screw up existing styles?

@benlesh
Copy link

benlesh commented Apr 29, 2014

You could do this a few ways...

1. Use composition:

Here you could just take the linking functions from the other directives and call them. This is a little "brute force" I suppose, but it's legal and straight forward.

function watLinker(elem, scope, attrs) {
   // wire up wat
}

function fooLinker(elem, scope, attrs) {
   // wire up foo
}

function barLinker(elem, scope, attrs) {
  // wire up bar
}

module.directive('amWat', function(){
   return watLinker;
});

module.directive('amFoo', function(){
   return fooLinker;
});

module.directive('amBar', function(){
   return barLinker;
});

module.directive('amTrifecta', function(){
    return function() {
       watLinker.apply(this, arguments);
       fooLinker.apply(this, arguments);
       barLinker.apply(this, arguments);
    }
});

2. Recompile the node:

Here you're going to just add the other attributes to the already existing node and tell it to rebind with $compile. Realistically, this is just doing the same thing I showed above. Just in a weird, roundabout way.

module.directive('amTrifecta', function ($compile) {
    return function(scope, elem, attrs) {
       var args = attrs.amTrifecta.split(' '); //assuming space-delimited
       elem.attr('am-wat', args[0]);
       elem.attr('am-foo', args[1]);
       elem.attr('am-bar', args[2]);
       $compile(elem)(scope);
    };
});

There are other ways to do this, but those are the two I could think of quickly off of the top of my head.

@ryanflorence
Copy link

Angular doesn't provide anything for you, here's one idea:

app.directive('am-trifecta', function() {
  return {
    scope: {},
    restrict: 'A',
    link: function($scope, el) {
      am.wat($scope, el);
      am.foo($scope, el);
      am.bar($scope, el);
    }
  };
});

Maybe @iammerrick has a better idea.

Edit: I have been beaten to the punch! Guess I'll get some water.

@benlesh
Copy link

benlesh commented Apr 29, 2014

If you were using element-based directives, your choices would actually be a little more broad, oddly enough.

... let's assume all of the directives are element based, you could do something weird like this:

something like this might work:

app.directive('amTrifecta', function() {
   function {
      restrict: 'E',
      template: '<am-wat><am-foo><am-bar ng-transpile></am-bar></am-foo></am-wat>',
      transpile: true,
      replace: true
   };
});

but I'm actually not sure, I've never made anything like that.

In the end though, my "option 1" above and what @rpflorence is proposing above are probably sub-optimal, because you wouldn't be able to do that if the directives wat, foo, and bar were coming from an external module, as you wouldn't likely have access to their linking functions. So you'd probably need to use "option 2" above for what you're trying to do.

@rpflorence ... also, you wouldn't want to use scope:{} and isolate the scope here, since the three directives your applying will want to be able to get the parent scope.

@machty ... other Angular weirdness, if any one of your directives wat, foo or bar, happened to isolate scope, you might see some strange behavior, as that scope may or may not be passed to one of the other directives, depending on the order in which the directives are applied.

@machty
Copy link
Author

machty commented Apr 29, 2014

@Blesh very cool, thanks for going through it all.

As I mentioned on Twitter (i'll try to keep the rest of the discussion here), I think you meant to write transclude instead of transpile.

Also, I don't understand why you'd have more flexibility with am-trifect being element-restricted; it's my understanding that the angular compiler just uses directive restrictions to determine a match and decide whether a directive's behavior gets applied to any given element, regardless of whether the match is class/element-based.

As for scopes, it'd be desirable if each directive got its own isolate scope, and I wouldn't have been surprised if it worked that way. Seems strange that scope would be decided in a somewhat non-deterministic manner based on element rather than each directive getting its own, but I'm sure there's something I'm not considering here.

@benlesh
Copy link

benlesh commented Apr 29, 2014

You're correct transpile was a typo.

The only reason you might have more flexibility with it element transpiled, is really just because it makes it more straight forward to replace the element with the other directives. Although I suppose you could still do that if they were attribute-based directives. I don't know... I was tired. ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment