简体   繁体   中英

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

The question

  • I have a list of projects, built with ng-repeat .
  • Each project has a context menu with edit/delete project links.
  • Context menus are built with custom dropdown-list directive from the array of objects like {title: "Link title", href: "/some-href"} .

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();
            });
          }
        ]
      };
    }
  ])

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];
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM