【问题标题】:Know when child element has loaded知道子元素何时加载
【发布时间】:2017-02-12 15:42:22
【问题描述】:

我在 Aurelia 中创建了三个与 Google 地图相关的元素,我以嵌套方式使用它们,其中我有一个基本 GoogleMap 元素,除此之外,我还有一个 GoogleMapLocationPicker 元素和顶部那个一个GoogleMapAutocomplete元素。

这一切都很好,但是我遇到的问题是,当我尝试从我的GoogleMapAutocomplete 元素中访问我的孩子GoogleMap 元素时,我必须将我的代码放入一个任意的setTimeout 或者我尝试访问不存在的属性时出错。

我真的不喜欢在setTimeout 上拍 200 毫秒并希望它永远不会失败,所以我想知道是否有一些官方方法可以知道我的子元素何时加载? (不使用 EventAggregator 作为最后的手段)。

我基本上有这个(请注意代码非常简化,在某些情况下完全不正确,但这仅用于演示目的 - 实际的谷歌地图代码工作正常):

谷歌地图

export class GoogleMap {
    @bindable markers;

    constructor () {
        this.map = new google.maps.Map(this.element);

        this.markers.forEach((marker) => {
            this.addMarker(marker);
        });
    }
}

<template>
    <div class="google-map"></div>
</template>

google-map-location-picker

export class GoogleMapLocationPicker {
    constructor {
        this.markers = [{
            draggable: true,
            dragend: e => {
                alert('Nice drag');
            }
        }];
    }
}

<template>
    <google-map markers.bind="markers" view-model.ref="map"></google-map>
</template>

google-map-autocomplete

export class GoogleMapAutocomplete {
    constructor () {
        this.autocomplete = new google.maps.places.Autocomplete();

        // This fails unless I wait like 200ms because GoogleMap hasn't yet created its map
        this.map.map.map.controls.push(this.autocomplete);
    }
}

<template>
    <google-map-location-picker view-model.ref="map"></google-map-location-picker>
</template>

更新:

我创建了一个 Plunker 来进一步解释这一点:http://plnkr.co/edit/8fVbjZhJoC8tzAfQOmKP?p=preview

如您所见,应用程序包括 &lt;parent-element&gt;(在我的情况下,这将是 GoogleMapAutocomplete),它反过来又包括 &lt;child-element&gt;(在我的情况下为 GoogleMap)。

子元素做了一些异步操作,只有在完成之后我才能从父元素访问子元素的属性。正如您在parent-element.js 中看到的,试图提醒孩子的财产返回undefined

我如何从parent-element.js 知道child-element.js 何时完成所有异步工作?

【问题讨论】:

    标签: aurelia custom-element


    【解决方案1】:

    这里最简洁的解决方案可能是在子元素完成后调度 CustomEvent,然后将其委托给父元素,如下所示:

    (我以你的 plunkr 为基础)

    child-element.js 中:

    import { inject } from "aurelia-framework";
    
    @inject(Element)
    export class ChildElement {
      constructor (element) {
        this.isLoaded = false;
        this.element = element;
      }
    
      attached () {
        setTimeout(() => {
          this.isLoaded = true;
          this.aPropertyOnlyAvailableAfterLoadingIsComplete = true;
          this.element.dispatchEvent(new CustomEvent("loaded", {
              bubbles: true
          }));
        }, 2000);
      }
    }
    

    parent-element.html 中:

    <child-element view-model.ref="child" loaded.delegate="onChildLoaded($event)">
    </child-element>
    

    parent-element.js 中:

    onChildLoaded($event) {
        alert(this.child.aPropertyOnlyAvailableAfterLoadingIsComplete);
    }
    

    查看this gist 了解正在运行的版本

    编辑(事件聚合器与自定义事件)

    您对使用 Event Aggregator 和 CustomEvent 之间的区别的评论是一个重要问题,所以我会在这里回答。

    博文working with the aurelia event aggregator 给出了一些额外的提示:

    它允许您触发没有特定目标的事件, 多个听众可能正在寻找的事件。他们 不是必要的,他们就像观察者,当事情发生时 订阅者方法会收到通知,并允许您采取相应措施。

    一切都是有代价的,所以不要滥用它并为每一个人使用它 更改您的应用程序,这非常适用于以下情况 跨组件通信是必不可少的。

    1. 没有特定目标的事件

    在这种情况下,您的目标是某个元素的父级。这是一个非常具体的目标,应该表明 CustomEvent 可能是最合适的。

    1. 不要在应用程序中的每次更改都使用它

    对于应用程序中的每次更改,都可以轻松使用事件聚合器。所有这一切都会奏效。除了依赖它的组件(如 aurelia-router)的潜在时序问题外,过度使用事件聚合器会降低密切相关组件之间的凝聚力,而几乎没有收益。

    我想补充一点:

    1. 当组件紧密耦合/紧密相关时使用 CustomEvent,当组件松散耦合时使用 Event Aggregator

    2. 当组件绑定到 DOM(例如自定义元素)时使用 CustomEvent,当它们没有绑定到 DOM 时使用事件聚合器(例如独立模块/类,在这种情况下不能使用 CustomEvent)

    3. 对于与 DOM 相关的组件,如果 CustomEvent 无法以干净、直接的方式解决您的问题(例如使用同级元素),事件聚合器可能是一个更好的主意

    4. 使用事件聚合器意味着您需要处理订阅,这会增加工作量和复杂性。

    5. 事件聚合器是您需要导入的依赖项,而 CustomEvents 是 JavaScript 原生的

    CustomEvent 通常更易于实现/管理,并且在使用上具有更明确的限制。它们还使视图更具可读性(考虑 some-event.delegate="onSomeEvent" 而不是必须扫描两个类以查找发布/订阅方法及其键 + 处理程序)。

    出于这个原因,我倾向于从 CustomEvent 开始,只有当它不起作用/变得过于复杂时,我才会求助于 Event Aggregator。

    【讨论】:

    • 谢谢。在这种情况下,您会说 CustomEvent 比 Aurelia 的 EventAggregator 更好吗?你能解释一下为什么会这样吗? (我想了解两者的区别和好处)。
    【解决方案2】:

    我倾向于使用if.bind 函数。我不确定您的哪些元素依赖于另一个,但听起来自动完成和位置选择器依赖于要呈现的基本 GoogleMap。所以,我对我的项目所做的就是在我的虚拟机中声明一个InitDataLoaded = false。然后,一旦我加载了所有初始数据,我会将其更改为true。在 html 中我会做&lt;myComponent if.bind="InitDataLoaded"&gt;&lt;/myComponent&gt;

    当然,您必须对其进行调整,但我认为if.bind 是您的选择。仅当条件为真时才渲染元素。

    进一步解释我(我自己)会做什么:

    我会在我的主要 GoogleMaps 组件中添加 InitDataLoaded 布尔值。然后在构造函数中,完成启动组件所需的所有工作后,将该布尔值从 false 更改为 true

    现在,在您的位置选择器和自动完成功能中,我将使用 @autoinject@inject 将 GoogleMaps 组件的实例注入虚拟机。从那里,您可以访问该InitDataLoaded 变量并使用if.bind 来呈现组件,前提是该布尔值是true

    我添加了一个工作示例的要点,而没有使用超时。 https://gist.run/?id=2e02bc4cc7236093937b964b5f546be0

    【讨论】:

    • 但是如果我在&lt;google-map-autocomplete&gt; 上执行if.bind,它的子元素肯定也不会呈现吗?
    • @powerbuoy 我刚刚编辑了我的答案,以解释如果我在你的鞋子里我会做什么。没错,它不会渲染 的任何子元素,但是为什么要在不渲染依赖组件之前渲染它呢?
    • 我需要尝试一下以验证这是否解决了我的问题。现在的问题是我重写了我的所有组件以不相互继承:P 但是你的意思是,在我的 GoogleMap 子组件中,我将有一个类似isLoaded 的属性,然后我会检查该属性我的父母 ?我真的不知道如何使用if.bind,因为我不想渲染我的组件(这会导致其子组件也不渲染,因此isLoaded 永远不会真的)。
    • 谢谢,Gistrun 看起来确实更好。感谢您的帮助,但是该代码仍然是在父级之后加载子级的另一种方式。我需要父母等孩子。我认为 Fred 明白了。
    • @powerbuoy 哦,明白了。是的,我误解了你的问题
    猜你喜欢
    • 1970-01-01
    • 2013-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-22
    • 1970-01-01
    • 2013-08-03
    • 2014-03-14
    相关资源
    最近更新 更多