推迟大小计算直到 ui 呈现

Posted

技术标签:

【中文标题】推迟大小计算直到 ui 呈现【英文标题】:Deferring size calculations until ui rendered 【发布时间】:2014-10-01 02:15:26 【问题描述】:

这是我的标记:

<div resizer>
    <div class="a"></div>
    <div class="b">
        This content generated dynamically using ng-include and other directives inside
    </div>
</div>

第二个 div (.b) 改变了它的高度(动态添加的元素)。我写了一个指令,应该根据可用高度更新第一个 div (.a):

'use strict';
myModule.directive('resizer', ['$timeout', function ($timeout) 
    return 
        restrict: 'A',
        link: function (scope, element, attrs) 

            function updateSizes(event) 
                var a = element.find('.a');
                var b = element.find('.b');

                var bHeight = b.outerHeight();
                var totalHeight = element.height();
                a.height(totalHeight - bHeight);
            

            element.on('some-event', updateSizes);
            $timeout(updateSizes);
        
    ;
]);

当我启动我的应用程序时,有时我的 div 高度为零(高度:0),因为 b.outerHeight();和 element.height();返回零。 这是为什么?我使用了 $timeout 以便浏览器有时间呈现视图。 有什么想法吗?

【问题讨论】:

The Angular Way 是一个常见的好问题。 AngularJS 有一个自然的流程,其中不断变化的模型重新定义了内容或样式。所以通常情况下,改变内容之类的“角度方式”就是 html 方式。似乎您想调整 div 的大小以适应内容。但是,如果我对你的理解正确,你做的工作太多了。 Div 本质上是为了根据内容调整大小。因此,除非您为 div 分配固定高度或宽度,否则它应该按照您的意愿行事。如果您想要额外的行为(填充、居中、固定宽度),这将是 CSS 答案而不是“Angular”。 这应该由css代替。 @DaveA 这无法使用 CSS 完成,因为我还需要在运行时更新尺寸。我将更新我的代码 - 还绑定到导致大小更新的事件。现在清楚了吗? @Naor,仍然不清楚。我错过了一些大事。在我看来,Div 会自动根据内容调整大小。由于我已经绑定了模型数据或内容,并对其进行了更改(或更改了指令以使用不同的模板),因此我的 div 已调整大小。不是说 CSS 是你的答案,我想念为什么 div 需要任何调整大小 - ergo,使用范围变量更改内容或使用 ng-style 指令更改样式,并且 div 应该相应地更改。正如在这种类型的误解中经常发生的那样,我可能遗漏了您的功能目标的一部分。 比代码更新更好(有用)将是一个小提琴和解释它不能做什么以及需要改变什么! 【参考方案1】:

您有两种可能的选择(请参阅ngInclude docs here):

    使用 ngInclude onload 事件 在加载内容时监听 ngInclude 发出的 $includeContentLoaded 事件

我建议使用第二个选项,因为这样可以节省您在每次要使用指令时指定 onload 事件。 您可以像这样在链接函数中指定侦听器:

function link(scope, element, attrs) 
    scope.$on('$includeContentLoaded', function()
        console.log('Template included');
        // you can add all your height calculation and DOM manipulation here
    );

看看this demo Plunker(我希望内容很不言自明)。 您的代码确实应该如下所示:

'use strict';
myModule.directive('resizer', ['$timeout', function ($timeout) 
    return 
        restrict: 'A',
        link: function (scope, element, attrs) 

            function updateSizes(event) 
                var a = element.find('.a');
                var b = element.find('.b');

                var bHeight = b.outerHeight();
                var totalHeight = element.height();
                a.height(totalHeight - bHeight);
            

            scope.$on('$includeContentLoaded', updateSizes);

        
    ;
]);

【讨论】:

这种方法的问题是 updateSizes() 调用了很多次(我在当前路由的某处有 ng-include 的时间)。我添加了一个计数器,这种方法发生了 20 多次。 好吧,如果你有 20 个指令,它当然会被调用 20 次。如果您有一些特定的逻辑要遵循(您只想调整某些元素的大小),您可以在指令中添加一个选项来添加侦听器。我建议你放一些其他代码来显示行为(一个 plunker 会更好)。 如果有嵌套指令,可以添加stopPropagation,防止父指令监听children事件。 但是,updateSizes() 将被调用不止一次(根据内部 ng-include 的数量) 此时您有不同的解决方案:this post 建议在所有包含已加载时触发事件。另一种选择可能是向您的指令添加一个属性,并有条件地仅在您需要的包含项上执行您的工作。否则,您可以向已经检查过的元素添加/删除一个类,或者在触发包含时取消绑定侦听器。如果没有看到您的嵌套代码,我无法向您推荐最好的。对不起。【参考方案2】:

方法

Angular 的一个常见模式是有一个嵌套指令需要一个父指令并将自己注册到父指令。您可以使用此模式来解决您的问题。我已将嵌套指令 resizer-sourceresizer-dest 添加到您的标记中:

<div resizer>
    <div class="a" resizer-dest></div>
    <div class="b" resizer-source>
        This content generated dynamically using ng-include and other directives inside
    </div>
</div>

大小调整源

myModule.directive('resizerSource', function () 
    return 
        require: '^resizer',
        link: function (scope, element, attrs, ctrl) 
          ctrl.registerSource(element);
        
    ;
);

大小调整目标

myModule.directive('resizerDest', function () 
    return 
        require: '^resizer',
        link: function (scope, element, attrs, ctrl) 
          ctrl.registerDest(element);
        
    ;
);

调整大小

myModule.directive('resizer', ['$timeout', function ($timeout) 
    return 
        controller: function($scope) 
            var a,b;
            function update() 
              if (a && b) 
                var bHeight = b.outerHeight();
                var totalHeight = element.height();
                a.height(totalHeight - bHeight);
              
            
            this.registerSource = function(elm) 
                b = elm;
                update();
            
            this.registerDest = function(elm) 
                a = elm;
                update();
            
        
    ;
]);

更加灵活

如果由于某种原因 b 的高度在 b 的链接功能运行时仍然不可用,您可以通过设置$watchb 的高度上并仅在手表评估为正数时注册 b。之后,取消观察者,除非您预计元素的高度会继续变化。

附加信息

如果我要 $watch b 的大小,我将不得不产生一个摘要循环(通过调用 $apply() 或 $digest)。我正在通知 resizer 使用 dom 事件进行更新。你怎么看?

(1) $watch调整尺寸是一项昂贵的操作,因为每次检查高度时都会强制进行回流。所以尽快取消$watch。 (2) 不能保证 angular 会在正确的时间触发摘要循环,因此可能需要使用您提到的 dom 事件。

由于这些原因,使用$watch 可能根本不是最好的方法,但这确实取决于您的具体情况。

在链接函数中使用元素不是一个好习惯吗?

使用控制器而不是链接功能没有什么不合时宜的。它们有不同的调用顺序,但这在这里不起作用。

您无权访问控制器中的“元素”。将调整大小代码放在链接函数中是否安全?我的意思是,我必须将 registerSource 和 registerDest 放在控制器上

如果需要,您可以将$element 注入控制器。或者,您可以将指令自己的控制器添加到require 属性,以从link 函数访问它。无论您选择将代码放入控制器还是 link 函数通常都归结为组织,所以不要害怕使用控制器而不是 link 函数,如果它可以简化您的代码。

【讨论】:

听起来是正确的方法。我会试试的。关于你对 $watch 的建议,如果我要 $watch b 的大小,我将不得不引起一个摘要循环(通过调用 $apply() 或 $digest)。我正在通知 resizer 使用 dom 事件进行更新。你怎么看? 另一个问题,您在调整大小控制器上使用了“a”和“b”元素。它有效吗?在链接函数中使用元素不是一个好习惯吗? 此外,您无法访问控制器中的“元素”。将调整大小代码放在链接函数中是否安全?我的意思是,我必须将 registerSource 和 registerDest 放在控制器上...... 我添加了一个部分来回答您的问题。【参考方案3】:

您的问题似乎是指令模板中有ng-include

ng-include 本身就是一个指令,它创建了一个新的范围。但是,您的 resizer 指令 link 函数会在呈现 HTML 后立即执行,因此 ng-include-d 模板已经存在,但其内容尚未加载(它甚至是单独的 HTTP/GET 请求如果模板之前没有加载)。

我确信还有更多的修复方法,但其中一种方法是使用 ng-include 标签的 onload 参数:

// ... your directive ...
link: function (scope, element, attrs) 
  function updateSizes(event) 
    console.log (element.find('.b').outerHeight());
  

  scope.ngOnload = updateSizes;

现在进入你的 html 模板:

<div resizer>
  <div class="a"></div>
  <div class="b">
    <div ng-include="'sometemplate.html'" onload="ngOnload()"></div>
  </div>
</div>

这对我有用,div.b 元素 outerHeight 在控制台日志中不再为 0。


我明白了,您已经使用 $timeout 来等待浏览器。如果我的解决方案对您不起作用,那么这些div 中一定有一些奇怪的东西,可能需要更多的解决方案细节。然而,debugger 函数中带有 chrome 调试器的 debugger 语句可能会有很大帮助。

【讨论】:

这听起来合乎逻辑,但问题是我有几个嵌套的 ng-includes。我应该把onload放在哪里?我应该等到整个 dom 都被渲染吗?怎么样? @Naor 那是另一个问题,解决方案实际上取决于您想要实现的目标,不仅从用户的角度来看,而且从代码基础架构来看。【参考方案4】:

我怀疑这个问题可能是由于加载 ng-include 资源的延迟造成的。您可以预加载缓存:

$templateCache.put('template.html', '<b>Template</b>');

或者如果内容来自 $http 调用:

$http.get('template.html', cache:$templateCache);

【讨论】:

延迟的原因有很多。模板加载、模板渲染、摘要循环等。我正在寻找一种在渲染结束时做某事的方法。你试图避免延迟,但这是不可避免的。 是的,但是由于 Angular 加载模板的异步特性,再加上您需要完全渲染 DOM,这可能意味着您需要预加载模板(并且还使用 $timeout ) 作为解决方案的一部分 让我告诉你,我正在使用 $templateCache 将我的所有模板连接到一个大 js 文件中,并且此脚本与应用程序 js 连接。所以这里没有 $http 请求。不过,我有延迟。【参考方案5】:

我知道这不是您要寻找的答案,但您为什么要走这么远才能让这些简单的事情发挥作用?

The best programming is, no programming

我只推荐你为此目的的 css 解决方案

<style>
.tbl display:table
.tbl-row display:table-row
.tbl-cell display:table-cell
</style>

<div class="tbl">
  <div class="tbl-row">
    <div class="tbl-cell">Foo</div>
    <div class="tbl-cell">
        This content generated dynamically using ng-include and other directives inside
    </div>
  </div>
</div>

示例:http://plnkr.co/edit/H81p2yu0jG8OuB8paFpM?p=preview

【讨论】:

你检查了吗?此代码在一行中生成两个 div,而不是两行。 我认为这不值得 plnkr。我加了一个 我刚刚意识到我的问题并不太清楚。这两个 div 一个在另一个之上,而不是另一个的权利。两行而不是两个单元格。这就是我改变高度而不是宽度的原因。

以上是关于推迟大小计算直到 ui 呈现的主要内容,如果未能解决你的问题,请参考以下文章

如何推迟 Tableau 中的计算?

声传播推迟时间计算对飞行器部件噪声预测的影响

声传播推迟时间计算对飞行器部件噪声预测的影响

声传播推迟时间计算对飞行器部件噪声预测的影响

推迟变量分配,直到文件存在或在 Makefile 中执行规则

复合组件参数在ui:repeat var属性时不会计算