Aurelia 知道何时加载了子元素

Aurelia 知道何时加载了子元素,aurelia,custom-element,Aurelia,Custom Element,我在Aurelia中创建了三个与Google地图相关的元素,并以嵌套方式使用它们,其中我有一个基本的Google地图元素,最上面有一个GoogleMapLocationPicker元素,最上面是GoogleMapAutocomplete元素 这一切都很好,但我遇到的问题是,当我试图从GoogleMapAutocomplete元素中访问我的子GoogleMap元素时,我必须将代码放入任意的setTimeout中,否则我会因为尝试访问不存在的属性而出错 我真的不喜欢在setTimeout上加200m

我在Aurelia中创建了三个与Google地图相关的元素,并以嵌套方式使用它们,其中我有一个基本的Google地图元素,最上面有一个GoogleMapLocationPicker元素,最上面是GoogleMapAutocomplete元素

这一切都很好,但我遇到的问题是,当我试图从GoogleMapAutocomplete元素中访问我的子GoogleMap元素时,我必须将代码放入任意的setTimeout中,否则我会因为尝试访问不存在的属性而出错

我真的不喜欢在setTimeout上加200ms,希望它永远不会失败,所以我想知道是否有某种官方方法可以知道我的子元素何时加载?没有使用EventAggregator,我可能会做最后的选择

我基本上有这一点,请注意,代码非常简单,在某些情况下完全不正确,但这只是出于演示目的-实际的谷歌地图代码工作良好:

谷歌地图

谷歌地图位置选择器

谷歌地图自动完成

更新:

我创建了一个Plunker来进一步解释这一点:

正如你们所看到的,在我的例子中,这个应用程序包括谷歌地图自动完成(GoogleMapAutocomplete),而在我的例子中,它又包括一个谷歌地图

子元素执行一些异步操作,只有完成这些操作后,我才能从父元素访问子元素的属性。正如您在parent-element.js中看到的,试图警告子级的属性返回未定义

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

我倾向于使用if.bind函数。我不确定哪些元素依赖于其他元素,但听起来像是自动完成和位置选择器依赖于要渲染的基本Google地图。因此,我对我的项目所做的是在我的vm中声明InitDataLoaded=false。然后,一旦我加载了所有初始数据,我会将其更改为true。在html中,我会这样做

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

为了进一步解释我自己会做什么:

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

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


我添加了一个工作示例的要点,没有使用超时

这里最干净的解决方案可能是在完成子元素时分派CustomEvent,然后将其委托给父元素,如下所示:

我把你的枪作为基础

在child-element.js中:

在parent-element.html中:

有关正在运行的版本,请参阅

编辑事件聚合器vs CustomEvent

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

博客文章给出了一些额外的提示:

它允许你发射没有特定目标的事件, 可能有多个侦听器在关注的事件。他们 当事情发生时,他们就像观察者一样,不是必要的 订户方法将被通知,并允许您相应地采取行动

每件事都是有代价的,所以不要滥用它,把它用在每一件事上 在您的应用程序中进行更改,这非常适用于以下情况: 跨组件通信至关重要

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

不要在应用程序的每次更改中都使用它 对于应用程序中的每个更改,使用事件聚合器都很容易。所有这些都会起作用。除了依赖它的组件(如aurelia router)可能存在的时间问题外,过度使用事件聚合器将降低密切相关组件之间的内聚性,几乎没有任何好处

我想补充一点:

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

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

对于与DOM绑定的组件,如果CustomEvent不能以干净、直接的方式解决问题(例如使用同级元素),那么事件聚合器可能是一个更好的主意

使用事件聚合器意味着您有子CR 要处理的iption增加了工作量和复杂性

事件聚合器是需要导入的依赖项,而CustomEvents是JavaScript固有的

CustomEvents通常更易于实现/管理,并且在使用上有更明确的限制。它们也使视图更可读,考虑一些事件。委托= OnEvror与扫描/订阅方法以及它们的密钥+处理程序的两类扫描。


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

但是如果我使用if.bind on,它的子元素肯定也不会渲染?@powerbullet我刚刚编辑了我的答案,解释了如果我处在你的位置,我会怎么做。没错,它不会渲染的任何子元素,但为什么要在不渲染依赖组件之前渲染它呢?我需要尝试一下,以验证这是否解决了我的问题。现在的问题是,我重写了我的所有组件,以避免相互继承:P但你的意思是,在我的GoogleMap子组件中,我将有一个类似isLoaded的属性,然后我将从父组件检查该属性?我不知道如何使用if.bind,因为我不想不渲染我的组件,这会导致其子组件也不渲染,因此isLoaded永远不会是真的。谢谢,Gistrun看起来确实更好。我很感谢你们的帮助,但对于孩子在父母之后加载代码的情况,代码仍然是另一种方式。我需要父母等孩子。“我想弗雷德明白了。”Powerbucket噢,明白了。是的,我误解了你的问题,谢谢。在这种情况下,您认为CustomEvent比Aurelia的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>
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);
  }
}
<child-element view-model.ref="child" loaded.delegate="onChildLoaded($event)">
</child-element>
onChildLoaded($event) {
    alert(this.child.aPropertyOnlyAvailableAfterLoadingIsComplete);
}