Building Recursive Interfaces in Angular and Entity Framework

I had an interesting problem to study today.  The business problem is quite simple.  I want to allow users to enter a number of items on a screen that are recursively related.  To illustrate here is what I have in mind

  • Item 1
    • Item 1.1
    • Item 1.2
      • Item 1.2.1
        • Etc..

I do not want to limit the user in the number of layers they want to enter. 

My first problem to solve is related to entity framework.  I want to return exact structure you see below.  All my items are stored in the same table, with parent Item ID being the foreign key.

    public partial class Item
    {
        public Item()
        {
            this.Item1 = new List<Item>();
        }

        public int ItemId { get; set; }
        public Nullable<int> ParentItemId { get; set; }
        public string Name { get; set; }
        public int SortOrder { get; set; }
        public bool IsDeleted { get; set; }
        public virtual ICollection<Item> Item1 { get; set; }
        public virtual Item Item2 { get; set; }
    }

Now, in my repository class I need to create the same tree structure.  I want to close the connection before returning the data, as I do not want to rely on lazy loading.  So, in my repository I want to recursively populate child collection.  Here is how I want to do it.

        public IEnumerable<Item> All()
        {
            var list = Context.Items
                .Where(one => !one.IsDeleted && one.ParentItemId == null)
                .OrderBy(one => one.ParentItemId)
                .ThenBy(one => one.SortOrder)
                .ThenBy(one => one.Name).ToList();
            list.ForEach(PopulateChildren);
            return list;
        }

        private void PopulateChildren(Item item)
        {
            var children = Context.Items
               .Where(one => !one.IsDeleted && one.ParentItemId == item.ItemId)
               .OrderBy(one => one.ParentItemId)
               .ThenBy(one => one.SortOrder)
               .ThenBy(one => one.Name).ToList();

            item.Item1 = children;
            foreach (var child in item.Item1)
            {
                PopulateChildren(child);
            }
        }

Because I handle recursion in the data layer, my Web Api method is very simple.

var data = Repository<IRepository>().All();
var mapped = data.Select(MappingHelper.Map<Item>).ToList();
return mapped;

Now I need to create a view.  Here is where things become complicated.  In my example I want to use Bootstrap panel for each item.  In my Angular controller I am going to just expose the list items (top items) as the model on the scope, using $scope.model property.  Now in my view (MVC view in my case) I want to use ng-repeat to loop over top level items.

    <div class="panel-group" id="accordion" ng-cloak>
        <div class="panel panel-default" ng-repeat="item in model" ng-form="editItem">
            <div class="panel-heading">
                <h4 class="panel-title">
                    <a data-toggle="collapse" data-no-click data-target="#item__{{item.ItemId}}" href="#item__{{item.ItemId}}">
                        {{item.Name}}
                    </a>
                </h4>
            </div>
            <div id="item__{{item.ItemId}}" class="panel-collapse collapse">
                <div class="panel-body">
                    <div ng-include="'itemTemplate.tmpl'"></div>
                </div>
            </div>
        </div>
    </div>

 

As you can see, I show item name in panel header, then in panel body I am using Angular template.  Template is the key of my design – It will be used recursively.

<script type="text/ng-template" id="itemTemplate.tmpl">
    <div class="row col-xs-12">
        @{
            Html.CustomInput<Item, string>(item => item.Name, "item", "editItem");
            Html.CustomInput<Item, int>(item => item.SortOrder, "item", "editItem");
        }
        <div class="panel-group" id="accordion">
            <div class="panel panel-default" ng-repeat="item in item.Children" ng-form="editItem">
                <div class="panel-heading">
                    <h4 class="panel-title">
                        <a data-toggle="collapse" data-no-click data-target="#item__{{item.ItemId}}" href="#item__{{item.ItemId}}">
                            {{item.Name}}
                        </a>
                    </h4>
                </div>
                <div id="item__{{item.ItemId}}" class="panel-collapse collapse">
                    <div class="panel-body">
                        <div ng-include="'itemTemplate.tmpl'"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</script>

 

If you take closer look at the template, you will see that I am building field inputs first, then I insert one more accordion, and in that one I loop over Children property of current item to build UI for current item’s children.  Finally, I create panel body that is using exact same template!  This is how I handled recursive implementation throughout all my levels.  By the way, Children property exists in my business model that corresponds to Item class in the Entity Framework level.  In that business object I rename Item2 property to Children, and map it accordingly in AutoMapper.

 MappingHelper.CreateOneWayMapping<Data.Models.Item, Item>()
    .ForMember(dest => dest.Children, config => config.MapFrom(source => source.Item1));

Thanks.

Enjoy.

5 Comments

  1. That’s OK with me Sergey… don’t worry about code sample, I took time to re-review your article and
    managed to make my sample work adding custom Automapper method…I guess that was the small part missing that made the whole difference… Thank’s 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *