模板始终使用指令中的旧范围值进行编译 [英] Template always compiles with old scope value in directive

查看:22
本文介绍了模板始终使用指令中的旧范围值进行编译的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个这样工作的指令:

.)

解决方案

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

您看似延迟绑定问题的原因与摘要周期以及 Angular 如何管理对 DOM 的更改有关.当正在处理范围更改时,范围观察者在对 DOM 进行任何更改之前被处理.这样,所有 DOM 更改都会合并为一个批次(至少在该周期中),因此您不会一次进行多次更新,这可能会导致多次回流.因此,当您调用 element.html() 时,您是在 DOM 尚未更新以反映作用域上更改的值的点上执行此操作.

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

该逻辑都可以像这样合并(并正常工作):

setTimeout(function () {var 编译 = templateFn(scope).html();var prettified =prettyPrintOne(已编译);element.html(美化);}, 0);

setTimeout 中包装所有内容会强制逻辑在摘要循环完成后被评估.

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

  • 如果每次使用都需要 HTML 模板(例如

     标签),则应将其包含在指令本身中通过 templatetemplateUrl 属性,而不是期望消费者知道它是必需的
  • 您可能可以在不使用 $compile 的情况下实现这一点 - 您可以将 prettyPrintOne 的输出放在范围上,然后在指定的模板中绑定到它在 templatetemplateUrl 属性中,或者您可以使用 jQuery 获取对将成为容器的任何元素的引用(即,如果它不是顶级元素)并使用 html() 设置其内容.
  • 如果您确实需要允许在指令中定义其他模板化 HTML 内容,请查看 指令指南.

I've got a directive that's working like this:

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

The problem is that when I compile the template, it always compiles with the old value of the target property. So it starts off showing this, i.e., it's acting like there's nothing to replace:

Then if I add a character to the property, it shows this, i.e., the previous value of the scope.organization.message property:

Debugging shows that the values in the target property of the scope directive are correct at the time of the compile.

What am I doing wrong? Is there something about the template function returned by $compile that looks at the old scope values? Or...?

(See also this question, which led to this one: Using $compile in a directive triggers AngularJS infinite digest error.)

解决方案

First off, your templateFn variable is scoped at the factory level, but it is populated at the instance level. This means that the first time you use the directive, it will populate using that element's directive, and every usage after that will also use that same template, even if it actually has a different template.

The cause of your seemingly delayed binding issue has to do with the digest cycle and how Angular manages changes to the DOM. When a scope change is being processed, the scope watchers are all processed before any changes are made to the DOM. This way, all DOM changes are consolidated into a single batch (for that cycle at least) so you aren't make multiple updates at once, potentially causing multiple reflows. So, when you're calling element.html(), you're doing so at a point where the DOM hasn't been updated to reflect the changed values on the scope.

In this particular case, you're also doing a bunch of extra work - calling templateFn will give you a jQuery (or jQLite) object with the content you need - there's no need to add it to the DOM, and then take it back out, you can just call html() directly against it.

That logic could all be consolidated (and work correctly) like so:

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

Wrapping everything in setTimeout forces the logic to be evaluated after the digest cycle is complete.

However, generally speaking, that implementation of the directive is a bit awkward:

  • If there's HTML templating (e.g. <pre> and <code> tags) that is required for each usage, that should be included in the the directive itself via the template or templateUrl properties rather than expecting the consumer to know that it's required
  • You can probably get away implementing this without using $compile - you can either put the output of prettyPrintOne on the scope, and just bind to it in the template specified in the template or templateUrl properties, or you can use jQuery to get a reference to whichever element will be the container (that is, if it's not the top-level element) and use html() to set its content.
  • If you do actually need to allow other templated HTML content to be defined inside the directive, look into the transclude option defined in the directive guide.

这篇关于模板始终使用指令中的旧范围值进行编译的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆