1

I have the following angular app to create a menu of sections/products.

at present when rendered and hitting the 'add' button that is rendered within each li I want to add a section/product as a sub of that section however multiple new children are created.

ultimately I wish to display a form which when submitted will create the child but that is the next step. Right now I need to limit the scope to the current section and not have multiple bound clicks.

If you need more information please state and I will post in an edit.

Some sample data data.

{
    "sections":[
        {
            "name":"Flags",
            "sections":[
                {
                    "name":"Europe",
                    "sections":[],
                    "products":[
                        { "name": "France" },
                        { "name": "Germany" },
                        { "name": "Ireland" },
                        { "name": "England" }
                    ]
                },
                {
                    "name": "Africa",
                    "sections":[],
                    "products":[
                        { "name": "Egypt" },
                        { "name": "Nigeria" },
                        { "name": "Chad" }

                    ]
                },
                {
                    "name": "South America",
                    "sections":[],
                    "products": [
                        { "name": "Brasil" },
                        { "name": "Argentina" },
                        { "name": "Peru" }
                    ]
                }
            ],
            "products":[]
        },
        {
            "name": "Maps",
            "sections":[
                {
                    "name": "Africa",
                    "sections":[],
                    "products":[
                        { "name": "Egypt" },
                        { "name": "Nigeria" },
                        { "name": "Chad" }

                    ]
                },
                {
                    "name": "South America",
                    "sections":[],
                    "products": [
                        { "name": "Brasil" },
                        { "name": "Argentina" },
                        { "name": "Peru" }
                    ]
                }

            ],
            "products":[]
        }        
    ],
    "products":[]
}

The app.

'use strict';

var menuApp = angular.module('menuApp', []);

menuApp
    .directive('sections', function () {
        return {
            restrict: "E",
            replace: true,
            scope: {
                sections: '='
            },
            template: '<ul><section ng-repeat="section in sections" section="section" /></ul>'
        };
    })
    .directive('section', function ($compile) {
        return {
            restrict: "E",
            replace: true,
            scope: {
                section: '=section'
            },
            template: '<li class="section">{{section.name}} <button ng-click="addSub(section)">Add</button></li>',
            link: function (scope, element, attrs, controller) {
                if (angular.isArray(scope.section.sections)) {
                    element.append("<sections sections='section.sections'></sections>"); 
                    $compile(element.contents())(scope);
                }
                if(angular.isArray(scope.section.products)){
                    element.append("<products products='section.products'></products>"); 
                    $compile(element.contents())(scope);
                };
            },
            controller : function($scope){
                console.log($scope);
                $scope.addSub = function (section){
                    //console.log(section,'Adding Sub');
                    section.sections.push({"name":"Section","sections":[],"products":[]});
                };
            }
        };
    })
    .directive('products', function () {
        return {
            restrict: "E",
            replace: true,
            scope: {
                products: '='
            },
            template: '<ul><product ng-repeat="product in products" product="product"></product></ul>'
        };
    })
    .directive('product', function ($compile) {
        return {
            restrict: "E",
            replace: true,
            scope: {
                product: '='
            },
            template: '<li class="product">{{product.name}}</li>'
        };
    });

menuApp.controller('menuCtrl', function menuCtrl($scope,$http) {
    $http.get('/ajax/getvenuesmenu?venueID='+venueMenu.venueId).success(function(resp) {
        $scope.sections = resp;
    });

    $scope.add = function(data){
        data.push({"name":"Section","sections":[]});
    };   
});
2
  • create a demo in plunker...very hard to help troubleshoot without being able to play with code in console Commented Dec 9, 2013 at 3:04
  • not used plunk before - it is running with pretty much markup same on my machine; here is the link - plnkr.co/edit/3Mt2jw4ojO5N2xFqKPpt?p=preview Commented Dec 9, 2013 at 3:27

1 Answer 1

1

Took me a bit to figure it out but here's the basic problem, you are compiling the full contents of section 2 extra times and each compile seems to add a new event handler.

Instead of compiling the contents of element each time you make an append of new template, compile the template itself (outside of the DOM) and then append the compiled template. This way the ng-click handler doesn't get compiled again other than initial scope creation

Here's an abbreviated version with one template appended:

link: function (scope, element, attrs, controller) {
    if (angular.isArray(scope.section.sections)) {
        /* compile outside of the DOM*/
        var subsections = $compile("<sections sections='section.sections'></sections>")(scope);
        /* append compilation*/
        element.append(subsections);        
    }

DEMO

Another approach would be to create a complete template string in link by checking for subsections and products, then compiling everything all at once....instead of using template option

Code for alternate approach compiling complete section at once:

.directive('section', function ($compile, $timeout) {
    return {
        restrict: "E",
        scope: {
            section: '=section'
        },
        link: function (scope, element, attrs, controller) {
            var template = '<li class="section">{{section.name}} <button ng-click="addSub(section)">Add</button>';

            if (angular.isArray(scope.section.sections)) {
                template += "<sections sections='section.sections'></sections>";
            }
            if (angular.isArray(scope.section.products)) {
                template += "<products products='section.products'></products>";
            };

            template += '</li>';

            var compiledTemplate = $compile(template)(scope);
            element.replaceWith(compiledTemplate);

            scope.addSub = function (section) {
                section.sections.push({ "name": "Section", "sections": [], "products": []
                });
            };       
        }
    };
})

DEMO-Alt

Sign up to request clarification or add additional context in comments.

1 Comment

Legend! - I have seen something using the compile property of a directive which seems related and potential performance improvements but this is what I needed - thank you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.