Директива Angular 1.x с шаблоном

#angularjs

#angularjs

Вопрос:

Я пытаюсь создать директиву angular для формирования предложений. Цель состоит в том, чтобы взять список и перебирать их по мере необходимости. Результатом директивы будет что-то вроде:

обувь, брюки и носки

или

обувь, брюки и еще 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 вместо шаблона директивы, но не смог найти решение.

Комментарии:

1. вы можете использовать ng-repeat=»статья в статьях», и это создаст необходимые теги «предложения».

2. Я понимаю, что могу использовать ng-repeat для перебора всех тегов. Моя цель, однако, состоит в том, чтобы сгенерировать предложение (т. Е. С «,» и «и»), которое пропускает, если предложение длиннее трех, показывая » еще 5″. Я знаю, как создать одноразовую версию, но хочу иметь возможность повторно использовать директиву, чтобы я мог использовать ее в нескольких областях приложения. Или я что-то упускаю?

3. То, что вы ищете, звучит для меня больше как фильтр

4. интересно… вы можете использовать функцию для создания списка … небольшой пример: «статья в queryResolver(‘обувь, брюки и носки’)… и затем эта функция возвращает список

5. @MilitelloVinx неясно, как фильтр помогает в этом случае (мне также нужно сгенерировать разделители).

Ответ №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 amp;amp; articles.length <= maxArticles">and</span>
</sentence>
<span ng-if="articles.length > maxArticles">
    and  {{articles.length - maxArticles}} more.
</span>
  

Ответ №2:

Итерация и предоставление динамического содержимого являются обычным использованием для пользовательской директивы с compile функцией $compile сервисом. Будьте осторожны: по сути, вы повторяете функциональность ng-repeat , возможно, вам захочется рассмотреть альтернативы.

Например, вместо articles списка используйте другой (возможно, с именем articlesLimited ). Новый список создается динамически и содержит первые элементы из articles . Флаг (например hasMore ) указывает, содержит ли оригинал articles больше элементов, просто как: $scope.hasMore = articles.length > 5 . hasMore Флаг используется для отображения / скрытия сообщения » N больше».

Однако, для чего это стоит, ниже приведена реализация sentence директивы. Слабые места см. В комментарии!

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

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

  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 amp;amp; 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 /

Ответ №3:

Это сложная проблема. Transclude используется для переноса элементов, но при использовании transclude у вас нет доступа к области действия директивы, только к области, в которой используется директива:

AnglularJS: создание пользовательских директив

Что именно делает эта опция transclude? 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.

Комментарии:

1. Я не включил в решение код для обработки размера списка и включения » 5 больше», но это простая часть.