Transclude function needs to modify clone properly
Asked Answered
D

1

1

I have a simple directive which repeats a section of transcluded content twice. Like this.

link: function (scope, element, attrs, controller, transclude) {

    transclude(scope.$parent, function(clone) {
        element.find('[transclude-main]').replaceWith(clone);
    });

    transclude(scope.$parent, function(clone) {
        element.find('[transclude-overflow]').replaceWith(clone);
    });

});

This works mainly as intended but if the content contains a form then I end up with two forms with the same name.

More importantly my main page controller (customers) only has reference to one of the forms (customers.myForm) so if I try to reset the form or call any other form controller functions only one of the forms changes, obviously.

So, I tried to modify my code to look for forms and change the form name to something new, like this.

link: function (scope, element, attrs, controller, transclude) {

    transclude(scope.$parent, function(clone) {
        element.find('[transclude-main]').replaceWith(clone);
    });

    transclude(scope.$parent, function(clone) {

        clone.find('FORM').each(function() {
            $(this).attr('name', $(this).attr('name') + '2');
        });
        element.find('[transclude-overflow]').replaceWith(clone);

    });

});

This does actually modify the HTML for me and I end up with two forms - myForm and myForm2.

The problem is that there is still only one reference to myForm in my main controller. The first one works but the second one doesn't. I can only assume that they are somehow compiled against the scope.$parent which I'm passing into the transclude function before I'm messing about with the clone? If that's the case I'm not sure how to fix it.

EDIT:

Added a plunkr here:

https://plnkr.co/edit/XE7REjJRShw43cpfJCh2

If you open a dev console you'll see that I'm using console.log to write out the contents of myForm and myForm2 which should be the two copies of the form in my second toolbar. myForm2 doesn't exist and I suspect this is because it's compiled against the parent scope before it's cloned.

Dariusdarjeeling answered 7/7, 2016 at 15:26 Comment(6)
What do you mean that "there is still only one reference to myForm in my main controller"? Can you link to a plunker?Garrot
I've not really used Plunkr but will give it a go.Dariusdarjeeling
Plunkr added:) I've also put a follow up question here to ask if this can be done without transclusion. If it can be answered on this question which has the bounty then I'm happy to delete the other question. #38333373Dariusdarjeeling
neither $scope.myForm nor $scope.myForm2 exist either in your plunkr (as printed from your 2 console.log's)?Garrot
in the plunkr it also looks like you are trying to find a FORM element that doesn't exist?Garrot
Sorry, plunkr hadn't saved the last few minutes of changes so I've updated the URL in the question. The content of the toolbar may or may not contain a form, so I've shown one with and one without. The first one has a say hello button which works in the main and overflow sections. The second has a form which has an ng-model which works even after the form has been cloned because they both point at the same model. The problem is that there is only one form reference in the controller which is why logging myForm2 doesn't work, even though it's in the DOM if you inspect it.Dariusdarjeeling
G
0

So here's a plunkr with I thinnnnk gets at what you're trying to do: https://plnkr.co/edit/8VxNPVmeLNLKyaQNReM3?p=preview

Note these lines in particular:

HTML:

  <toolbar name="myForm" form-one="myForm1" form-two="myForm2">
    <form name="myForm" submit="appController.submit()">
      Search:<br />
      <input type="text" ng-model="appController.searchText" />
    </form>
  </toolbar>

note that both name attributes point at the same string "myForm". form-one and form-two are normal two way bindings that can be bound to your scope properties of choosing, in my example, myForm1 and myForm2.

JS:

the two way binding definitions

    scope: {
      formOne: '=',
      formTwo: '='
    },

and binding the two new forms to respective scope attributes:

    link: function (scope, element, attrs, controller, transclude) {

        var parent = scope;

        transclude(function(clone, scope) {
            element.find('[transclude-main]').replaceWith(clone);
            var unwatch = scope.$watch(attrs.name, function(form) {
              if (form) {
                parent.formOne = form;
                unwatch();
              }
            });
        });

        transclude(function(clone, scope) {
            element.find('[transclude-overflow]').replaceWith(clone);
            var unwatch = scope.$watch(attrs.name, function(form) {
              if (form) {
                parent.formTwo = form;
                unwatch();
              }
            });
        });

Looking back at your code, this console console.log("myForm", $scope.myForm2) printed undefined because angular form directives are not dynamic. As a result, manually finding the html element and changing the name from myForm to myForm2 will not change the name of the form bound to scope unless the html has yet to be compiled. But the clone passed to transclude has been freshly compiled.

Garrot answered 12/7, 2016 at 21:26 Comment(3)
Thanks jbmilgrom but I have obviously not explained what I'm trying to do very well. I can't use bindings for the forms on the toolbar directive because the user of my directive can put literally anything into the toolbar, one form, no forms or even ten forms. Whatever they put in has to be cloned and in doing so I need to append a "2" to any form name (and indeed input name) which needs to have a unique name. The last paragraph you wrote is my problem because I need to clone the contents and then compile it. Not sure I can do this with transclusion, hence the follow-up question.Dariusdarjeeling
ahhh, yeah you may want to avoid transclusion then. All transcluded templates will be precompiled once you are given access to them. Think about using the compile property of the directive api instead to grab pre compiled template to manipulate. good luckGarrot
Thanks. I'm going to give you the reputation points because this has really helped me and technically it's the correct answer because I can't do what I was trying to do.Dariusdarjeeling

© 2022 - 2024 — McMap. All rights reserved.