gkond
gkond

Reputation: 4294

How can I pass $scope variables into string template of custom directive?

The question

I need to pass dynamic parameters for ng-href (project.id in my case), into my directive's template, which I use in ng-repeat. How can I achieve that?

That is my general question. I feel like I have significant misunderstanding of Angular JS concepts here and counting on your help.

What I've tried

I've tried passing array of strings into my directive, and I got unparsed href like this:

/projects/%7B%7B%20project.id%20%7D%7D/edit

Why is that?

projects-template.html

<li ng-repeat="project in data.projects">
    <a ng-href="/projects/{{ project.id }}">{{ project.title }}</a>
    <a dropdown-list="projectContextMenu"></a>

projects.controller.js

$scope.projectContextMenu = [
    {
      text: "Edit",
      href: "/projects/{{ project.id }}/edit"
    }, {
      text: "Delete",
      href: "/projects/{{ project.id }}/delete"
    }
  ];

I've also tried

passing a function returning an array of parsed strings, but got an endless recursion error:

10 $digest() iterations reached. Aborting!

How come?

projects-template.html

<li ng-repeat="project in data.projects">
    <a ng-href="/projects/{{ project.id }}">{{ project.title }}</a>
    <a dropdown-list="projectGetContextMenu(project.id)"></a>       

projects.controller.js

$scope.projectGetContextMenu = function(projectID){
    return [
        {
          text: "Edit",
          href: "/projects/" + projectID + "/edit"
        }, {
          text: "Delete",
          href: "/projects/" + projectID + "/delete"
        }
      ];
}

Dropdown directive code

angular.module("ngDropdowns", []).directive("dropdownList", [
    "$compile", "$document", function($compile, $document) {
      var template;
      template =
        "<ul>"+
        "  <li ng-repeat='dropdownItem in dropdownList' ng-class='{ \"active\": dropdownModel && dropdownModel.value == dropdownItem.value }'>"+
        "    <a href='' ng-href='{{ dropdownItem.href }}' ng-click='itemSelect(dropdownItem)'>{{ dropdownItem.text }}</a>"+
        "</ul>";
      return {
        restrict: "A",
        replace: false,
        scope: {
          dropdownList: "=",
          dropdownModel: "="
        },
        controller: [
          "$scope", "$element", "$attrs", function($scope, $element, $attrs) {
            var $template, $wrap;
            $template = angular.element(template);
            $template.data("$dropdownListController", this);
            $element.addClass("dropdown_selected").wrap("<div></div>");
            $wrap = $element.parent();
            $wrap.append($compile($template)($scope));
            $scope.itemSelect = function(dropdownItem) {
              if (dropdownItem.href) {
                return;
              }
              angular.copy(dropdownItem, $scope.dropdownModel);
              $wrap.removeClass("dropdown__active");
            };
            $document.find("body").on("click", function() {
              $wrap.removeClass("dropdown__active");
            });
            $element.on("click", function(event) {
              event.stopPropagation();
              $wrap.toggleClass("dropdown__active");
            });
            $wrap.on("click", function(event) {
              event.stopPropagation();
            });
          }
        ]
      };
    }
  ])

Upvotes: 1

Views: 1623

Answers (1)

Brian Genisio
Brian Genisio

Reputation: 48167

Your second approach is more correct because you need to construct different URLs based on the context. But like you saw, you get into an endless digest cycle.

This is because you are returning a different array reference every time

Angular sees it as being different, so requires another turn of the crank, which calls your function again, which returns a new array, etc, etc.

Your projectGetContextMenu function needs to cache the results, and return the same reference. Like this:

var contextMenus = {};

$scope.projectGetContextMenu = function(projectID){
    if(!contextMenus[projectId]) {

      contextMenus[projectId] = [
        {
          text: "Edit",
          href: "/projects/" + projectID + "/edit"
        }, {
          text: "Delete",
          href: "/projects/" + projectID + "/delete"
        }
      ];
    }

    return contextMenus[projectId];
};

Upvotes: 1

Related Questions