-
Notifications
You must be signed in to change notification settings - Fork 27.5k
Add node processing hook to $compile public API. #15270
Comments
This is an interesting idea. #6781 is indeed more involved and probably not suitable for 1.x at this point, but a more "lightweight" alternative might be a useful addition. Regarding the first usecase (i.e. adding a directive whenever another directive is used), it is possible today - although admittedly not in the most straight-forward of ways 😃 Basically, you can have a high-priority, terminal directive (which means it will stop the compilation), that attaches the extra attribute and the comtinues the compilation (for lower priorities): .component('mySubmenu', { ... })
.directive('genericStateListener', function() { ... })
.directive('mySubmenu', function($compile) {
// DDO
return {
restrict: 'E',
priority: 2000,
terminal: true,
compile: function mySubmenuCompile(tElem) {
tElem.attr('genericStateListener', '');
var compiled = $compile(tElem, null, 2000);
return function mySubmenuPostLink(scope) {
compiled(scope);
};
}
};
}) |
So, yeah, I was aware of that method, however, if instead I want to check for a condition, as opposed to a particular directive, it becomes more difficult. I actually think I figured out a way to, with the way the public API is already, but it was only after initial directive collection had taken place. That meant that to actually apply the directive, I'd have to recompile it, and if a bunch of directives satisfy said conditions, then I didn't want to call $compile on all of them. I thought that might be a pretty intense performance hit (not super well-versed in how performance-intensive $compile is, however, so that may be a silly issue to be concerned about). |
Checking a condition should also be possible with the same technique: .component('mySubmenu', { ..., $isStateListener: true })
.directive('genericStateListener', function() { ... })
.directive('mySubmenu', function($compile, $injector) {
// DDO
return {
restrict: 'E',
priority: 2000,
terminal: true,
compile: function mySubmenuCompile(tElem) {
var dirs = $injector.get('mySubmenuDirective');
var isStateListener = dirs.some(function (dir) {
return dir.controller && dir.controller.$isStateListener;
});
if (isStateListener) tElem.attr('genericStateListener', '');
tElem.attr('genericStateListener', '');
var compiled = $compile(tElem, null, 2000);
return function mySubmenuPostLink(scope) {
compiled(scope);
};
}
};
}) Obviously this is a very verbose and inconvenient technique and that is why I still thing your proposal sounds interesting. I am just pointing out what is possible today (in case someone needs it). |
Sorry, when I said "check a condition", I meant check for a condition in ALL directives. The example you gave still only works to add a directive to all |
Additionally, your solution still results in, essentially, a double compilation, right? |
True. You could device some hack for certain situations (e.g. monkey-patching ATM, I don't think there is a way to apply a directive to all elements.
Not exactly. Since my directive is terminal, only directives with a priorirty >=2000 will be compiled and then, it will manually compile for directives with a priority <2000. (BTW, double-compilation - in the sense of re-compiling an already fully compiled element - is not supported and may lead to all sorts or memory leaks and unexpected behavior.) |
Mmm, ok, that's good to know. It also further enforces the idea that this could be a clean/simple way of doing something that's currently not available by other means, right? So, would a pull request be considered if I submitted one? Or are we still just waiting on opinions from more of the angular team? |
I would like to get more opinions. My main concern is that we generally try to avoid introducing features that are difficult or impossible to map to Angular 2, as that would make it much harder for people to migrate their apps. And I am not sure if something similar is possible in ng2 in a straight-forward way. |
Oh, that totally makes sense. I guess I just assumed there was probably a way of doing this in ng2, since similar functionality had been sitting in the icebox for angular for a while. |
There seems to be a parallel conversation happening on the ng2 repo: angular/angular#8785, though I would argue that this feature is slightly more overarching than JUST having the option to add directives to the host element within the component declaration, but that does seem to be the most common use case. |
@gkalpak so, is the fact that this got added to the backlog milestone a good thing, or a bad thing? |
@Aaron-Pool, it could be worse 😃 As discused above, it is better to wait and see which way Angular 2 goes. We can make a more informed decision then. So, let's keep an eye on angular/angular#8785. |
Will do, thanks! |
Do you want to request a feature or report a bug?
Feature
What is the current behavior?
I have no way of modifying a node before the majority of compile functionality takes place (that I've found, and yes, I've read the source :) ).
Suppose I want all components named
mySubmenu
to also have the directivegenericStateListener
applied to them. I've separated out a lot of ui-router state based functionality into it's own directive, in keeping with the separation of concerns principle. I don't want a bunch of generic state handling logic clogging up my component controller. Additionally, I may want to use this state logic on other components that are concerned with state transitions.Well, to do this, I have two options (that I know of):
genericStateListener
in every place I usemySubmenu
. Well, that's annoying :)mySubmenu
in a block which has thegenericStateListener
directive. This is workable, but it bothers me that someone else viewing my code needs to go into my template to understand that this component has state listening functionality. Additionally, if I want to interact with thegenericStateListener
directive controller, I cannot require it, because it's BELOW me now.Now the situation becomes even MORE complex (but also more simple) in the instance that, perhaps I don't want to be tied to a particular functionality/directive for listening to states. I want to abstract it further. In fact, I just want to be able to put an
isStateListener
field on my component. Then, by overriding the.component
decorator, I can add an annotated field$stateListener
every time I see this field on myoptions
object. GREAT I now have a way to check if the user wants to have stateListener functionality on the component I'm compiling! These are the kinds of things I can EASILY do already in angular's current state. There's only one problem, even once I get said attribute on the final directive object... I can't do anything with it.There's no way of modifying a node prior to the start of the compilation process (before directives are collected). By the time I can add functionality to the $compile process, directives have already been collected. This seriously hampers extensibility of the component object.
What is the expected behavior?
I would expect there to be a publicly accessible function to be run somewhere in the
collectDirective
function, like this:This would enable me to implement so much additional functionality in a clean, concise, and for the sake of other developers on my project, abstractly. For example, suppose I'm using https://github.com/tenphi/angular-bem, and I want all components to also have a block
directive on them for simple, clean BEM notation. I could just do this:
And voila! It's done! I'll admit that there are some parts of the compilation process that I don't fully get, and maybe this will have unintended consequences that I don't understand, but it seems like a simple solution to a problem that provides extensibility that is more generic that traditional specific directive decorators.
There was a similar pull request done by @lgalfaso , here: #6781, it seemed to have gotten some good feedback, but it was pretty involved, and it looks like it got shelved.
I'd be glad to create a pull request, if I thought this functionality had any chance of being merged.
-Aaron
The text was updated successfully, but these errors were encountered: