Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternatives to replace:true for angularJS directives

Tags:

angularjs

So replace: true is being removed from AngularJS in the near future. I understand why, but I think certain scenarios for developing in AngularJS are not well supported without it.

I want to create a button component that looks like this:

<button ng-click="doSomething()" important-model="outerCtrl.theModel">
    important text
</button>

That is initiated from an attribute, for example, like this:

<div my-button-component>
</div>

Now using replace I get what I want, but since it is deprecated I would like to avoid using it, so I think maybe I can do this:

<button my-button-component ng-click="doSomething()" important-model="outerCtrl.theModel">
</button>

And let the directive insert the 'important text' inside.

This solution is weird because although ng-click has access to the directive scope and will work as expected (as though it were inside the directive template), the fact that my component is a button, and is clickable, and whatever else should be defined inside my component, not in the template where I choose to use it.

Another option is just to have a div wrapper for all of that, which is not acceptable due to front-end constraints.

A final option is to accept that I cannot control the root element's type or attributes using a directive, and to progammatically apply a click listener and any other behaviours (e.g. ng-class). This doesn't seem like a good solution to me, although it is probably the best so far.

This isn't the first time I've felt like something is wrong with how much control you have over the root element of a directive, am I thinking about this wrong?

like image 753
Peter Ashwell Avatar asked Feb 11 '23 18:02

Peter Ashwell


1 Answers

First, I don't think replace: true is getting removed in the near future. It has been deprecated, but getting rid of it in any angular 1.X release would be a breaking change so I think you're ok until Angular 2.0 is released. Angular 2.0 is going to break a lot more than that, so I wouldn't worry too much about how to make existing apps work without replace: true. It's likely that 2.0 will have some kind of equivalent functionality, but migrating an existing app to 2.0 will probably not be something you'll want to do.

However, to answer your question hypothetically:

replace: true does two things:

1) It replaces the element in the DOM with the directive template.

2) It merges attributes from the element in the DOM with the directive template.

(2) is actually the non-trivial part of the feature, and the edge cases around it are the reason the Angular team wants to get rid of it.

For instance, if your element is:

<div some-directive ng-click="doSomething()"><div>

And someDirective has a template:

<div ng-click="doSomethingInDirective()"></div>

Which takes precedence after you've replaced the node? The logic for replace: true currently makes those decisions and wires everything up for you.

If, for some reason, replace: true gets removed from 1.X (it won't), what do you do?

The answer is you will have to manually do what replace: true does now, which (simplified) is:

1) Get the original element's attributes

2) Merge the original element's attributes with the directive template, keeping track of attributes that are directives and attributes that are not, and also keeping track of where each directive was declared (on the original element or in the template).

3) Compile the directive template by compiling the merged attribute directives and the directive template directives.

4) Link the merged attribute directives that were part of the original element to the $parent scope and all other directives to the directive scope. Link the directive to the directive scope

4a) Perform transclusion if necessary (transclude: true and template includes ng-transclude)

5) Replace the original element with the fully linked directive template.

Here is the relevant code from Github:

if (directive.replace) {
            replaceDirective = directive;
            if (jqLiteIsTextNode(directiveValue)) {
              $template = [];
            } else {
              $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
            }
            compileNode = $template[0];

            if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
              throw $compileMinErr('tplrt',
                  "Template for directive '{0}' must have exactly one root element. {1}",
                  directiveName, '');
            }

            replaceWith(jqCollection, $compileNode, compileNode);

            var newTemplateAttrs = {$attr: {}};

            // combine directives from the original node and from the template:
            // - take the array of directives for this element
            // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed)
            // - collect directives from the template and sort them by priority
            // - combine directives as: processed + template + unprocessed
            var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs);
            var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1));

            if (newIsolateScopeDirective) {
              markDirectivesAsIsolate(templateDirectives);
            }
            directives = directives.concat(templateDirectives).concat(unprocessedDirectives);
            mergeTemplateAttributes(templateAttrs, newTemplateAttrs);

I wouldn't go out and write your own version of that anytime soon. Any code that requires replace: true will also require Angular 1.X, so it will be supported. When you are ready to write a new app with Angular 2.0, there will be another (hopefully better) way to do this.

like image 140
Joe Enzminger Avatar answered Feb 13 '23 09:02

Joe Enzminger