Search code examples
angularjsangularjs-ng-transclude

Angular Transclude issue


How can I call an array by i rather than by # during a repeated transclude? With my code below, the repeat builds my list with the proper number of items in the array, but the data returned for x number of rows is only object[0] as specified in the markup. I am unable to reference i like I could in the repeat.

Am I thinking about this wrong or am I just missing something?

I have a template for an accordion like this

#### accordion.html ####
<ion-content scroll="false">
<ion-list>
    <ion-item class="item-stable alertHeader" ng-click="vm.toggleGroup()" ng-class="{active: vm.isGroupActive()}">
        <div class="row">
            <div class="alertHeaderIcon col-xs-1">
                <i class="icon" ng-class="{'icon-watchList':vm.tag == 'Tag1', 'icon-topPerformers':vm.tag == 'Tag2'}"></i>
            </div>
            <div class="alertHeaderLabel col-xs-9">
                <span class="labelName">{{vm.tag}}</span>
            </div>
            <div class="alertHeaderWarning col-xs-1 pull-right">
                <span class="label label-pill label-default">7</span>
            </div>
            <div class="alertHeaderCaret col-xs-1 pull-right">
                <i class="icon" ng-class="vm.isGroupActive() ? 'ion-ios-arrow-down' : 'ion-ios-arrow-forward'"></i>
            </div>
        </div>
    </ion-item>
    <ion-item class="item-accordion" ng-repeat="i in vm.data" ng-show="vm.isGroupActive()">
        <div ng-transclude></div>
    </ion-item>
</ion-list>
</ion-content>

I then place this into my markup as followed:

#### alerts.html #### 
<ca-accordion class="accordianTest" tag="'Tag1'" data="vm.itemsA">
            <div class="row">
                <div class="home-alert-icon col col-10">
                    <i class="icon" ng-class="{'icon-watchList':vm.itemsA[0].wlName == 'labelA', 'icon-topPerformers':vm.itemsA[0].wlName == 'labelB'}"></i>
                </div>
                <div class="home-alert-title col col-70">
                    {{vm.itemsA[0].wlName}}
                </div>
                <div class="home-alert-retailers">
                    {{vm.itemsA[0].wlRetailers}}
                </div>
                <div class="home-alert-metric col col-20">
                    {{vm.itemsA[0].wlPercent}}
                </div>
        </div>
</ca-accordion>

Solution

  • What you are missing is that the transcluded content, lives in a sibling scope to which the directive have been called, and not in the scope of your ng-repeat. so your thats why i isn't reachable here:

    <ca-accordion class="accordianTest" tag="'Tag1'" data="vm.itemsA">
                <div class="row">
                    <div class="home-alert-icon col col-10">
                        <i class="icon" ng-class="{'icon-watchList':vm.itemsA[0].wlName == 'labelA', 'icon-topPerformers':vm.itemsA[0].wlName == 'labelB'}"></i>
                    </div>
                    <div class="home-alert-title col col-70">
                        {{vm.itemsA[0].wlName}}
                    </div>
                    <div class="home-alert-retailers">
                        {{vm.itemsA[0].wlRetailers}}
                    </div>
                    <div class="home-alert-metric col col-20">
                        {{vm.itemsA[0].wlPercent}}
                    </div>
            </div>
    </ca-accordion>
    

    and only vm.itemsA is reachable.

    So a possible solution for that is either to move the whole ng-repeat outside, to the transcluded content,

    Or write your own directive, that will transclude with scope, meaning that the transcluded content will live inside the scope of your ng-repeat.

        /**
        @desc a copy of the ng-transclude implementation, but makes the transcluded content scope
        to be in the same scope of where the transcludeWithScope was used (ngTransclude behaivor
         bound it to the parent scope)
        */
    function transcludeWithScopeDirective() {
        return {
            restrict: 'EAC',
            bindToController: true,
            link($scope, $element, $attrs, controller, $transclude) {
                function ngTranscludeCloneAttachFn(clone) {
                    if (clone.length) {
                        $element.empty();
                        $element.append(clone);
                    }
                }
    
                // If there is no slot name defined or the slot name is not optional
                // then transclude the slot
                var slotName = $attrs.transcludeWithScope || $attrs.ngTranscludeSlot;
                $transclude($scope, ngTranscludeCloneAttachFn, null, slotName);
            },
    
    
        };
    }
    
    angular
        .module('transcludeWithScope', [])
        .directive('transcludeWithScope', transcludeWithScopeDirective);
    

    and then update your accordion.html to use the transcludeWithScope instead of ng-transclude:

     <ion-item class="item-accordion" ng-repeat="i in vm.data" ng-show="vm.isGroupActive()">
        <div transclude-with-scope></div>
    </ion-item>
    

    and your usage: now we can access i, as our transcluded content lives at the scope of our ng-repeat.

    <ca-accordion class="accordianTest" tag="'Tag1'" data="vm.itemsA">
                <div class="row">
                    <div class="home-alert-icon col col-10">
                        <i class="icon" ng-class="{'icon-watchList': i.wlName == 'labelA', 'icon-topPerformers': i.wlName == 'labelB'}"></i>
                    </div>
                    <div class="home-alert-title col col-70">
                        {{i.wlName}}
                    </div>
                    <div class="home-alert-retailers">
                        {{i.wlRetailers}}
                    </div>
                    <div class="home-alert-metric col col-20">
                        {{i.wlPercent}}
                    </div>
            </div>
    </ca-accordion>