Angular 1.x指令与模板

时间:2016-10-04 20:09:04

标签: angularjs

我正在尝试创建一个用于形成句子的角度指令。目标是获取一个列表并根据需要迭代它们。该指令的结果如下:

  鞋子,裤子和袜子

  

鞋子,裤子和+5更多

我有基本的指令设置来处理字符串数组 - 但是我想自定义它以允许每个句子元素的自定义模板(即超链接,样式等)。那就是:

<sentence values="article in articles">
<strong>{{article.title}}</strong> by <span>{{article.author}}</span>
</sentence>

用户在浏览器中看到的HTML必须是:

$scope.articles = [
  { title: '...', author: '...'},
  { title: '...', author: '...'},
  ...
]

<span><strong>ABC</strong> by <span>123</span></span>
<span>, </span>
<span><strong>DEF</strong> by <span>456</span></span>
<span>and</span>
<span>+5 more</span>

我猜这与transclude有关,但无法弄清楚API。我还尝试使用ng-repeat而不是指令模板,但无法找到解决方案。

3 个答案:

答案 0 :(得分:1)

这样的事情应该适用于maxArticles是在您的范围内定义的数字

<sentence values="article in articles | limitTo: maxArticles">
    <strong>{{article.title}}</strong> by <span>{{article.author}}</span>
    <span ng-if="$index < maxArticles - 2">, </span>
    <span ng-if="$index === articles.length - 1 && articles.length <= maxArticles">and</span>
</sentence>
<span ng-if="articles.length > maxArticles">
    and +{{articles.length - maxArticles}} more.
</span>

答案 1 :(得分:0)

迭代 AND 提供动态内容是使用compile函数+ $compile服务的自定义指令的常见用法。注意:基本上你正在重复ng-repeat的功能,你可能想要考虑替代方案。

E.g。而不是articles列表,使用另一个(可能名为articlesLimited)。新列表是动态构建的,包含articles中的第一个元素。标记(例如hasMore)表示原始articles是否包含更多元素,简称为:$scope.hasMore = articles.length > 5。您使用hasMore标志来显示/隐藏“+ N more”消息。

然而,对于它的价值,下面是sentence指令的实现。看弱点的评论!

app.directive('sentence', ['$compile', function($compile) {
  var RE = /^([a-z_0-9\$]+)\s+in\s([a-z_0-9\$]+)$/i, ONLY_WHITESPACE = /^\s*$/;

  function extractTrimmedContent(tElem) {
    var result = tElem.contents();
    while( result[0].nodeType === 3 && ONLY_WHITESPACE.test(result[0].textContent) ) {
      result.splice(0, 1);
    }
    while( result[result.length-1].nodeType === 3 && ONLY_WHITESPACE.test(result[result.length-1].textContent) ) {
      result.length = result.length - 1;
    }
    return result;
  }

  function extractIterationMeta(tAttrs) {
    var result = RE.exec(tAttrs.values);
    if( !result ) {
      throw new Error('malformed values expression, use "itervar in list": ', tAttrs.values);
    }
    var cutoff = parseInt(tAttrs.cutoff || '5');
    if( isNaN(cutoff) ) {
      throw new Error('malformed cutoff: ' + tAttrs.cutoff);
    }
    return {
      varName: result[1],
      list: result[2],
      cutoff: cutoff
    };
  }

  return {
    scope: true, // investigate isolated scope too...
    compile: function(tElem, tAttrs) {
      var iterationMeta = extractIterationMeta(tAttrs);

      var content = $compile(extractTrimmedContent(tElem));
      tElem.empty();

      return function link(scope, elem, attrs) {
        var scopes = [];
        scope.$watchCollection(
          function() {
            // this is (IMO) the only legit usage of scope.$parent:
            // evaluating an expression we know is meant to run in our parent
            return scope.$parent.$eval(iterationMeta.list);
          },
          function(newval, oldval) {
            var i, item, childScope;

            // this needs OPTIMIZING, the way ng-repeat does it (identities, track by); omitting for brevity
            // if however the lists are not going to change, it is OK as it is
            scopes.forEach(function(s) {
              s.$destroy();
            });
            scopes.length = 0;
            elem.empty();

            for( i=0; i < newval.length && i < iterationMeta.cutoff; i++ ) {
              childScope = scope.$new(false, scope);
              childScope[iterationMeta.varName] = newval[i];
              scopes.push(childScope);
              content(childScope, function(clonedElement) {
                if( i > 0 ) {
                  elem.append('<span class="sentence-sep">, </span>');
                }
                elem.append(clonedElement);
              });
            }

            if( newval.length > iterationMeta.cutoff ) {
              // this too can be parametric, leaving for another time ;)
              elem.append('<span class="sentence-more"> +' + (newval.length - iterationMeta.cutoff) + ' more</span>');
            }
          }
        );
      };
    }
  };
}]);

小提琴:https://jsfiddle.net/aza6u64p/

答案 2 :(得分:0)

这是一个棘手的问题。 Transclude用于包装元素,但是当使用transclude时,您无法访问指令范围,只能访问使用该指令的范围:

AnglularJS: Creating Custom Directives

  

这个转换选项究竟做了什么? transclude使得带有此选项的指令的内容可以访问指令之外的范围而不是内部。

所以解决方案是创建另一个组件来将模板的范围注入到指令中,如下所示:

.directive('myList', function() {
  return {
    restrict: 'E',
    transclude: true,
    scope: { items: '=' },
    template: '<div ng-repeat="item in items" inject></div>'
  };
})

.directive('inject', function() {
  return {
    link: function($scope, $element, $attrs, controller, $transclude) {
      $transclude($scope, function(clone) {
        $element.empty();
        $element.append(clone);
      });
    }
  };
})

<my-list items="articles">
    <strong>{{item.title}}</strong> by <span>{{item.author}}</span>
</my-list>

这是从这次讨论中得出的:#7874

我做了Plnkr