知道 child 元素何时加载

Know when child element has loaded

我在 Aurelia 中创建了三个 Google 地图相关元素,我以嵌套的方式使用它们,其中我有一个基础 GoogleMap 元素,在它之上我有GoogleMapLocationPicker 元素和 that 元素之上 GoogleMapAutocomplete 元素。

大部分情况下一切正常,但是我遇到的问题是当我尝试从我的内部访问我的 child GoogleMap 元素时GoogleMapAutocomplete 元素我必须将我的代码放在任意 setTimeout 中,否则我会在尝试访问 non-existent 属性时出错。

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

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

google-map

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-位置选择器

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-自动完成

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

如您所见,App 包含 <parent-element>(在我的例子中,这将是 GoogleMapAutocomplete),它又包含一个 <child-element>(在我的例子中,GoogleMap).

child 元素执行一些异步操作,只有在完成后我才能从 parent 访问 child 的属性。正如您在 parent-element.js 中看到的,试图提醒 child 的 属性 returns undefined.

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

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

您当然需要对其进行调整,但我认为 if.bind 是您的选择。它仅在条件为真时呈现元素。

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

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

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

时才渲染组件

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

这里最简洁的解决方案可能是在 child-element 完成后分派 CustomEvent,然后将其委托给 parent-element,如下所示:

(我以你的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

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

您关于使用事件聚合器和自定义事件之间的区别的评论是一个重要问题,因此我将在这里回答。

博客 post working with the aurelia event aggregator 提供了一些额外的建议:

It allows you to fire off events that don’t have a specific target, events potentially multiple listeners could be looking out for. They are not essential, they are like observers, when something happens the subscriber methods are notified and allow you to act accordingly.

Everything comes with a cost, so don’t abuse it and use it for every change in your application, it is very much for those situations where cross component communication is essential.

  1. 没有特定目标的事件

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

  1. 不要将它用于应用程序中的每个更改

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

我想补充一点:

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

  2. 当组件绑定到 DOM(例如自定义元素)时使用 CustomEvent,当组件未绑定时使用事件聚合器(例如 stand-alone modules/classes, 在这种情况下不能使用 CustomEvent)

  3. 对于 DOM-tied 组件,其中 CustomEvent 无法干净地解决您的问题,straight-forward 方式(例如,使用同级元素),事件聚合器可能是更好的主意

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

  5. Event Aggregator 是您需要导入的依赖项,而 CustomEvents 是 JavaScript

  6. 的原生依赖

CustomEvents 通常比 implement/manage 更简单,并且在使用中有更明确的限制。它们还使视图更具可读性(考虑 some-event.delegate="onSomeEvent" 而不是必须扫描 类 以获得 publish/subscribe 方法及其键 + 处理程序)。

出于这个原因,我倾向于从 CustomEvent 开始,只有当它不起作用/变得太复杂时,我才求助于事件聚合器。