OK, I swear I've searched all over SO for answers to this. Each answer I'm finding is almost, but not quite, exactly not what I'm looking for.
Goal
I'm trying to write some directives that let me DRY up the way I display collections. Final goal is to be able to do things in the view or another directive that looks like:
<md-grid-tile index='event' index-by='isPublic' index-key='true'>
<md-grid-tile-header>{{event.name}}</md-grid-tile-header>
<md-grid-tile-footer>
<profile-card index='eventOwner' index-by='event_id' index-key='event.$id' collection='profile'>
arbitrary transcluded content that uses {{profile}}
</profile-card>
</md-grid-tile-footer>
</md-grid-tile>
md-grid-tile-*
all come from angular-material, and do fancy transcluding things with their contents.
profile-card
is some arbitrary custom directive that wraps transcluded content. E.g. i have a common card to display profile info, but i want to put different action buttons on it depending on where i'm placing it.
I'm trying to create the index
directive, which will act like a very focused ng-repeat. In this example:
- The attrs on
md-grid-tile
element tell it to repeat over everyevent
with anisPublic
oftrue
. - On the
profile-card
element, it will find alleventOwner
objects with anevent_id
matching theevent.$id
of the containing element. It will then repeat theprofile
objects that thoseeventOwner
objects reference.
Problems
I've got no problems with hooking up to the data sources, where I run into problems is:
- Creating multiple elements in the DOM in the right place
- Repeating elements that transclude content
For the purposes of code samples here I am simplifying the data sources. mockProvider
will be replaced with a service that creates the collection based on the index-*
attrs.
Attempt #1
.directive 'index', ['mockProvider', (mockProvider)->
restrict: 'A'
priority: 1
compile: (el,attrs)->
el.attr 'ng-repeat', 'item in collection'
post: (scope,el,attrs,ctrl,transclude)->
scope.collection = mockProvider
]
I still haven't been able to figure out why I can't get this approach to work. The ng-repeat
attr gets properly attached, but nothing is repeated.
Attempt #2
.directive 'index', ['mockProvider', (mockProvider)->
restrict: 'A'
priority: 1 # tried with 1, 1001, -1000
compile: (el,attrs)->
post: (scope,el,attrs,ctrl,transclude)->
scope.collection = mockProvider
newEl = angular.element el[0].outerHTML
newEl.attr 'ng-repeat', 'foo in [1,2,3]'
newEl.removeAttr 'pb-index'
el.replaceWith $compile(newEl[0].outerHTML)(scope)
]
This repeats a bunch of stuff, but then explodes with: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found.
It seems like the re-compilation somehow loses the transcluded content? I've also seen other SO comments that warn against this due to inefficiency.
Attempt #3
.directive 'index', ['mockProvider', (mockProvider)->
restrict: 'A'
priority: 1001
compile: (el,attrs)->
post: (scope,el,attrs,ctrl,transclude)->
el.html ""
scope.collection = mockProvider
scope.$watchCollection 'collection', (collection)->
for item,index in collection
childScope = scope.$new()
childScope.item = item
transclude childScope, (newElement)->
el.append newElement
Finally, I tried a naive re-implementation of ng-repeat
. This has gotten the closest, but still isn't working, and requires a bunch more code to get it to match the performance/functionality of ng-repeat
.
HOW???
Am I totally on the wrong track? Is there an easy way to do this?