Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Add node processing hook to $compile public API. #15270

Open
@Aaron-Pool

Description

@Aaron-Pool

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 directive genericStateListener 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):

  1. I could place the directive genericStateListener in every place I use mySubmenu. Well, that's annoying :)
  2. I could wrap my template for mySubmenu in a block which has the genericStateListener 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 the genericStateListener 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 my options 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:

  switch (nodeType) {
        case NODE_TYPE_ELEMENT: /* Element */
          // use the node name: <directive>
          runNodeTransforms(nodeName_(node),node); // function exposed on $compileProvider
          addDirective(directives,
              directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);

          // iterate over the attributes
          for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
                   j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
            var attrStartName = false;
            var attrEndName = false;

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:

function componentDecoratorConfig($compileProvider) {
        "ngInject";
        var oldProvider = $compileProvider.$get,
            oldComponent = $compileProvider.component;

        $compileProvider.component = registerComponent;

        function registerComponent(name, options) {
            options.$isComponent = true;
            return oldComponent.bind($compileProvider)(name, options);
        }

       $compileProvider.addNodeTransform(function(name, node) {
            if($injector.has(name + 'Directive') 
                && $injector.get(name + 'Directive').controller.$isComponent) {
                node.attr.set('block', name);
            }
            return node;
        });

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions