知道子元素何时加载

时间:2016-10-04 12:53:38

标签: aurelia custom-element

我在Aurelia中创建了三个与Google Map相关的元素,并且我以嵌套的方式使用它们,我有一个基础GoogleMap元素,最重要的是我有一个GoogleMapLocationPicker元素和 一个GoogleMapAutocomplete元素。

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

我真的不喜欢在setTimeout上打200ms而希望它永远不会失败,所以我想知道是否有一些官方方式知道我的子元素何时加载? (不使用我可能作为最后手段的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>

谷歌地图位置的拾取器

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>

谷歌地图的自动完成

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

正如您所看到的,应用包含<parent-element>(在我的情况下,这将是GoogleMapAutocomplete例如),而它又包含<child-element>(在我的情况下为GoogleMap )。

子元素执行一些异步操作,只有在完成后才可以从父级访问子级的属性。正如您在parent-element.js中看到的那样,试图提醒孩子的属性返回undefined

parent-element.js如何知道child-element.js何时完成了所有异步内容?

2 个答案:

答案 0 :(得分:3)

这里最干净的解决方案可能是在子元素完成后调度一个CustomEvent,然后.delegate到父元素,如下所示:

(我把你的傻瓜作为基础)

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

EDIT(Event Aggregator vs CustomEvent)

您对使用Event Aggregator和CustomEvent之间的区别的评论是一个重要问题,因此我将在此处回答。

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

  

它允许您触发没有特定目标的事件,   潜在的多个听众可能会关注的事件。他们   当事情发生时,他们就像观察者一样   通知订阅者方法并允许您采取相应行动。

     

一切都需要付出代价,所以不要滥用它并且每次都使用它   改变您的应用程序,非常适合那些情况   跨组件沟通至关重要。

  1. 没有特定目标的事件
  2. 在这种情况下,您的目标是某个元素的父级。这是一个非常具体的目标,应该是CustomEvent最适合的指标。

    1. 不要将其用于应用程序中的每次更改
    2. 对于应用程序中的每个更改,都可以轻松使用Event Aggregator。所有这一切都会奏效。除了依赖于它的组件(如aurelia-router)的潜在时序问题之外,过度使用事件聚合器将减少密切相关组件之间的内聚,几乎没有增益。

      我想补充一点:

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

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

      3. 对于DOM绑定的组件,其中CustomEvent无法以干净,直接的方式解决您的问题(例如使用兄弟元素),事件聚合器可能是一个更好的主意

      4. 使用Event Aggregator意味着您有dispose订阅,这增加了工作量和复杂性。

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

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

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

答案 1 :(得分:1)

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

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

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

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

现在,在您的locationpicker和自动填充功能中,我会使用@autoinject@inject将GoogleMaps组件的实例注入vm。从那里,您可以访问该InitDataLoaded变量,并仅在该布尔为if.bind时使用true呈现该组件。

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