模板始终使用指令中的旧范围值进行编译

时间:2014-12-16 18:16:17

标签: javascript angularjs angularjs-directive angularjs-scope

我已经得到了这样的指令:

http://jsfiddle.net/smithkl42/cwrgLd0L/23/

App.directive('prettify', ['$compile', function ($compile) {
    var templateFn;
    return {
        restrict: 'E',
        scope: {
            target: '='
        },
        link: function (scope, element, attrs) {
            if (!templateFn) {
                var template = element.html();
                templateFn = $compile(template);
            }
            scope.$watch('target', function (newVal, oldVal) {
                var compiled = templateFn(scope);
                element.html('');
                element.append(compiled);
                var html = element.html();
                var prettified = prettyPrintOne(html);
                element.html(prettified);
            }, true);
        }
    };
}]);

问题在于,当我编译模板时,它总是使用target属性的值进行编译。所以它开始显示这一点,即它表现得像没有什么可以替代的:

enter image description here

然后,如果我向属性添加一个字符,它会显示这个,即scope.organization.message属性的 previous 值:

enter image description here

调试显示scope指令的target属性中的值在编译时是正确的。

我做错了什么?是否有$compile返回的模板函数查看旧范围值?还是...?

(另请参阅此问题,导致此问题:Using $compile in a directive triggers AngularJS infinite digest error。)

1 个答案:

答案 0 :(得分:1)

首先,您的templateFn变量的范围是工厂级别,但它是在实例级别填充的。这意味着第一次使用该指令时,它将使用该元素的指令进行填充,之后的每个用法也将使用相同的模板,即使它实际上具有不同的模板。

看似延迟绑定问题的原因与摘要周期以及Angular如何管理对DOM的更改有关。在处理范围更改时,范围观察者将在对DOM进行任何更改之前进行处理。这样,所有DOM更改都合并为一个批处理(至少为该周期),因此您不会同时进行多次更新,从而可能导致多次重排。因此,当您调用element.html()时,您在DOM尚未更新以反映范围内已更改的值时这样做。

在这种特殊情况下,你还要做一些额外的工作 - 调用templateFn将为你提供一个jQuery(或jQLite)对象,其中包含你需要的内容 - 没有必要将它添加到DOM中,然后将其取回,您可以直接致电html()

这个逻辑可以像这样整合(并且正常工作):

setTimeout(function () {
    var compiled = templateFn(scope).html();
    var prettified = prettyPrintOne(compiled);
    element.html(prettified);
}, 0);

摘要周期结束后,将setTimeout中的所有内容包装为强制评估逻辑。

但是,一般来说,该指令的实现有点尴尬:

  • 如果每次使用都需要HTML模板(例如<pre><code>标签),则应通过template或{{1}将其包含在指令中}而不是期望消费者知道它是必需的
  • 您可以在不使用templateUrl的情况下实现此目的 - 您可以将$compile的输出放在范围上,然后在prettyPrintOne中指定的模板中绑定它或template属性,或者您可以使用jQuery来获取对容器的任何元素的引用(即,如果它不是顶级元素)并使用templateUrl来设置其内容。 / LI>
  • 如果您确实需要允许在指令中定义其他模板化HTML内容,请查看directive guide中定义的html()选项。