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

feat(ngTranscludeSelect): add ng-transclude-select directive for multi-transclusion #11736

Closed
wants to merge 1 commit into from

Conversation

kara
Copy link

@kara kara commented Apr 27, 2015

I've updated the PR to make the syntax closer to <content select> tags (and thus make migration to Angular 2 a bit easier). To transclude in multiple points, you simply:

  • set transclude to true
  • use ng-transclude-select directives in your template to mark transcludable sections
  • when using the directive, add the appropriate selectors to match the template's ng-transclude-select selectors.

Example usage:

index.html

<my-container>
   <div head>This is head content.</div>
   <div body>This is body content.</div>
   <div foot>This is foot content.</div>
</my-container>

myContainer.js

angular.module('app', [])
.directive('myContainer', function() {
   return {
      transclude: true,
      template: [
         '<header ng-transclude-select="[head]"></header>',
         '<main ng-transclude-select="[body]"></main>',
        '<footer ng-transclude-select="[foot]"></footer>'
      ].join('')
   };
});

The output in the DOM would be something like:

   <header ng-transclude-select="[head]">
      <div head>This is head content.</div>
   </header>
   <main ng-transclude-select="[body]">
      <div body>This is body content.</div>  
   </main>
   <footer ng-transclude-select="[foot]">
      <div foot>This is foot content.</div>
   </footer>

The implementation needs work. There are some hacks like wrapping the clone to use querySelector - would love to replace that with something better. Another implementation option would be to do all the matching at once - so you could throw an error at any unused clone elements (currently, they are just being cleaned up).

selectedElements,
transcludeTargets = dirElement[0].querySelectorAll('[ng-transclude-select]'),
cloneWrapper = jqLite("<span></span>");
cloneWrapper.append(clone);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely code smell, but I couldn't figure out a better way to search the clone by selector. Any suggestions on how to improve?

@kara kara force-pushed the multi-transclude branch 2 times, most recently from 12d35c2 to df45ab8 Compare May 1, 2015 03:29
@petebacondarwin
Copy link
Member

Hi @kara. Sorry I haven't got round to this yet. Trying to finish up the 1.4.0 issues.
Next week I promise!

The ng-translude-select directive allows users to transclude multiple templates into their directive.
@kara kara changed the title feat($compile): add multi option to transclude property feat($compile): add ng-transclude-select directive for multi-transclusion Jun 2, 2015
@kara
Copy link
Author

kara commented Jun 2, 2015

@petebacondarwin No problem! Know you've been busy with 1.4.

Whenever you have a chance, let me know if you have any suggested changes. There is at least one notable hack (regarding wrapping the clone) that I would love to replace with something better if possible.

@kara kara changed the title feat($compile): add ng-transclude-select directive for multi-transclusion feat(ngTranscludeSelect): add ng-transclude-select directive for multi-transclusion Jun 2, 2015
@petebacondarwin
Copy link
Member

I have added this to the list of things to discuss in our 1.5 planning

@petebacondarwin
Copy link
Member

@IgorMinar is going to take a look this week. There have been developments in the WebComponents API that we ought to take into account for this PR perhaps.

@petebacondarwin
Copy link
Member

We should consider making the API similar to the SLOT proposal for ShadowDOM: See https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Slots-Proposal.md.

@IgorMinar
Copy link
Contributor

The thing is that the slotting api style makes the implementation as well as usage of this feature more cumbersome. The only thing we'd gain is similarity with an evolving standard. I don't think that it is worth it in Angular 1.

One significant drawback of the current implementation is that it will transclude as many times as many instance of ngTranscludeSelect we find in the components template. This is pretty wasteful and will become a perf issue for bigger apps. Could we address this?

It's quite hard to find a good way for this transcluded dom to be shared, since it needs to be accessible from any child node that triggers the ngTranscludeSelect directive, but how about we monkey-patch the $transclude service in this case using a private ($$-prefixed) field?

@petebacondarwin
Copy link
Member

After talking with @IgorMinar we agreed that some form of slot based algorithm would make it easier to avoid the performance problems. I am going to make a quick spike this week. I can't remember if this is close to what @kara had in her previous PR. If we agree it is viable then perhaps, @kara, you would have time to work on the full implementation?

The idea would be that some directive say ng-slot would be used to provide a slot name to nodes in the HTML that is to be transcluded. When the compiler is compiling a directive that requests transclusion, it would remove these nodes, and provide named transclusion functions to the post link function, and so also to the ng-transclude directive:

Example:

<my-container>
   <div ng-slot="head">This is head content.</div>
   <div>This is body content.</div>
   <div ng-slot="foot">This is foot content.</div>
</my-container>
angular.module('app', [])
.directive('myContainer', function() {
   return {
      transclude: true,
      link: function(scope, element, attr, controller, transcludeFn, transcludeSlots) {
        // here the transcludeSlots parameter will look like:
        // { head: headTranscludeFn, foot: footTranscludeFn }
        // The transcludeFn will contain anything that was not in a slot
        // where each function is a pre-bound (to its own transclusion scope) transclude function
      },
      template: [
         '<header ng-transclude="head"></header>',
         '<main ng-transclude></main>',
        '<footer ng-transclude="foot"></footer>'
      ].join('')
   };
});

@kara
Copy link
Author

kara commented Sep 2, 2015

@IgorMinar @petebacondarwin Thanks for taking a look! The changes you're suggesting make a lot of sense. Yeah, I can definitely make time to rewrite the functionality with the slot implementation you outlined.

petebacondarwin added a commit that referenced this pull request Oct 12, 2015
Now you can efficiently split up and transclude content into specified
places in a component's template.

```html
<pane>
  <pane-title>Some content for slot A</pane-title>
  <pane-content>Some content for slot A</pane-content>
</component>
```

```js
mod.directive('pane', function() {
  return {
    restrict: 'E',
    transclude: { paneTitle: '?titleSlot', paneContent: 'contentSlot' },
    template:
    '<div class="pane">' +
      '<h1 ng-transclude="titleSlot"></h1>' +
      '<div ng-transclude="contentSlot"></div>' +
    '</div>' +
  };
});
```

Closes #4357
Closes #12742
Closes #11736
Closes #12934
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants