在编译AngularJS指令中的模板之前,先评估传入的文本

Evaluate transcluded text before compiling template in AngularJS directive

本文关键字:评估 文本 AngularJS 编译 指令      更新时间:2023-11-15

我正在AngularJS中处理一个"collapseText"指令。它的功能是显示最多"maxLength"个字符和一个"Read More"选项,如果文本太大,该选项将加载其余文本。

我希望我的指令能够屏蔽文本,包括表达式。

理想情况下,我希望它看起来像这样:

<collapse-text max-length="10">This text will be collapsed</collapse-text>
<collapse-text max-length="10">{{foo}}</collapse-text>

我使用的模板是:

<span>{{lessText}}</span>
<span ng-if="overflow">
    <span ng-if="!readMore" style="cursor:pointer" ng-click="toggleReadMore()">...(read more)</span>
    <span ng-if="readMore">{{moreText}}</span>
</span>

我的指令是这样的:

'use strict'
angular.module('myModule')
.directive('collapseText', function($window){
    return{
        restrict: 'E',
        scope: true,
        transclude: true,
        controller : function($scope){
            $scope.toggleReadMore = function(){
                $scope.readMore = true;
            }
        },
        link: function(scope, element, attrs, ctrl, transclude){
            scope.maxLength = attrs.maxLength;
/* 1. Evaluate transcluded element */
/* 2. Check transcluded element's length */
/* 3. Set lessText, moreText, readMore and overflow */
/* 4. Evaluate this directive's template */
            console.log(transclude(scope.$parent, function(compiled){
                scope.lessText = compiled.text().substring(0, scope.maxLength);
                scope.moreText = compiled.text().substring(0, scope.maxLength);
                scope.readMore = false;
                scope.overflow = scope.moreText ? true : false;
                return compiled.text();
            }).text());
        },
        templateUrl: "templates/collapse-text-template.html"
    }
});

完成步骤1-4的正确方法是什么?我看到的两个症状是:

  1. SOLVED:在溢出和readMore变量更新后,ng-if语句不会重新求值,因此,这些文本字段永远不会出现在DOM中。
    • 我通过将ng-if语句分别更改为"overflow===true"、"readMore===false"answers"readMore===true"来修复未重新求值的问题。我仍然希望能澄清一下为什么它不能简单地使用。主要的问题是,关于排除文本评估,仍然存在
  2. PENDING:{{foo}}将被打印为"{{foo}}",而不是"foo包含的文本"

提前感谢您的帮助!

在指令的link()函数中,您必须等待{{foo}}被求值并可以使用。这可以通过使用$timeout()在浏览器的事件循环中调度新任务来实现。我不确定这是否是最干净的解决方案,但至少它有效。

以下是您使用$timeout()的代码和一些小的改进:

<div ng-app="myModule" ng-controller="MyController">
    <collapse-text max-length="10">This text will be collapsed</collapse-text>
    <collapse-text max-length="10">{{foo}}</collapse-text>
</div>

template.html

<span ng-if="!readMore">{{lessText}}</span>
<span ng-if="overflow">
    <span ng-if="!readMore && overflow" style="cursor: pointer;" ng-click="toggleReadMore()">...(read more)</span>
    <span ng-if="readMore">{{moreText}}</span>
</span>

脚本

angular.module('myModule', []).controller('MyController', function($scope){
    $scope.foo = 'This text will also be collapsed';
});
angular.module('myModule').directive('collapseText', function($timeout){
    return {
        restrict: 'E',
        scope: true,
        transclude: true,
        controller: function($scope){
            $scope.toggleReadMore = function(){
                $scope.readMore = true;
            };
        },
        link: function(scope, element, attrs, ctrl, transclude){
            var maxLength = +attrs.maxLength;
            var compiled = transclude(scope.$parent);
            $timeout(function(){
                scope.lessText = compiled.text().substring(0, maxLength);
                scope.moreText = compiled.text();
                scope.readMore = false;
                scope.overflow = scope.moreText.length > maxLength;
            });
        },
        templateUrl: "template.html"
    }
});


请注意,此实现不会对$scope.foo的更新做出反应(即,指令不会看到更新和重新呈现)。如果需要,我建议您将内容传递给属性中的指令,并实现一个观察程序,而不是使用transclusion。例如:

angular.module('myModule').directive('collapseText', function(){
    return {
        restrict: 'E',
        scope: {
            myContent: '=',
            // ...
        },
        link: function(scope){
            scope.$watch('myContent', function(newValue){
                if (newValue !== undefined) {
                    doSomethingWith(newValue);
                }
            });
        },
        templateUrl: "template.html"
    }
});

正如Aletheios所建议的那样,我找到了一种方法,那就是在transclude文本上实现一个监视和一个手动transclude函数,该函数只需将原始输入存储在本地范围中。按照它的实现方式,它将在"lessText"字段中显示原始输入,稍后将由执行其转换的父级进行处理。此transclusion将触发collapseText指令中的监视。

我能想到一些可能发生的问题,尽管我不完全确定,因为我对AngularJS:还很陌生

  • 如果超过"maxLength"显示长度的文本被屏蔽,并且需要进一步处理,它会起作用吗?示例:

    <collapse-text max-length="10" {{myLongVariableName.anotherLongVariableName}}</collapse-text>
    
  • 此代码根本不允许使用带有任何标记的文本。示例:

    <collapse-text max-length="20"><b>I am bold</b></collapse-text>
    
  • 即使它确实允许带标记的文本,当将标记拆分为lessText和moreText时,也有可能破坏标记

解决方案

最初的测试表明,它可以很好地处理明文和AngularJS表达式。

使用.html

这里需要注意的一点是,我打算在另一个指令中使用此代码,因此{{parent.foo}}也应该工作。

<div ng-app="myModule" ng-controller="MyController">
    <collapse-text max-length="10">This text will be collapsed</collapse-text>
    <collapse-text max-length="10">{{foo}}</collapse-text>
</div>

template.html

<span ng-if="readMore === false">{{lessText}}</span>
<span ng-if="overflow === true">
    <span ng-if="readMore === false" style="cursor:pointer" ng-click="toggleReadMore()">...(read more)</span>
    <span ng-if="readMore === true">{{moreText}}</span>
</span>

脚本

angular.module("myModule")
.controller('MyController', function($scope){
    $scope.foo = 'This text will also be collapsed';
})
.directive('collapseText', function($window){
    return{
        restrict: 'E',
        scope: true,
        transclude: true,
        controller : function($scope){
            $scope.readMore = false;
            $scope.toggleReadMore = function(){
                $scope.readMore = true;
            }
            $scope.$watch(
                            function(){
                                return $scope.transcluded;
                            },
                            function(newValue){
                                if($scope.transcluded){
                                    $scope.lessText = $scope.transcluded.text().substring(0, $scope.maxLength);
                                    $scope.moreText = $scope.transcluded.text();
                                    $scope.readMore = false || $scope.readMore; //If it was arleady true, do not collapse it again.
                                    $scope.overflow = $scope.moreText ? true : false;
                                }
                            }
                        );
        },
        link: function(scope, element, attrs, ctrl, transclude){
            scope.maxLength = attrs.maxLength;
            transclude(scope.$parent, function(transcluded){
                scope.transcluded = transcluded;
            });
        },
        templateUrl: "template.html"
    }
});

在实现类似内容时,我将非常感谢有关代码和"最佳实践"的进一步反馈。