我在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
何时完成了所有异步内容?
答案 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提供了一些额外的指示:
它允许您触发没有特定目标的事件, 潜在的多个听众可能会关注的事件。他们 当事情发生时,他们就像观察者一样 通知订阅者方法并允许您采取相应行动。
一切都需要付出代价,所以不要滥用它并且每次都使用它 改变您的应用程序,非常适合那些情况 跨组件沟通至关重要。
在这种情况下,您的目标是某个元素的父级。这是一个非常具体的目标,应该是CustomEvent最适合的指标。
对于应用程序中的每个更改,都可以轻松使用Event Aggregator。所有这一切都会奏效。除了依赖于它的组件(如aurelia-router)的潜在时序问题之外,过度使用事件聚合器将减少密切相关组件之间的内聚,几乎没有增益。
我想补充一点:
当组件紧密耦合/紧密相关时使用CustomEvent,当松散耦合时使用Event Aggregator
当组件绑定到DOM(例如自定义元素)时使用CustomEvent,而当它们不绑定时使用Event Aggregator(例如独立模块/类,在这种情况下不能使用CustomEvent)
对于DOM绑定的组件,其中CustomEvent无法以干净,直接的方式解决您的问题(例如使用兄弟元素),事件聚合器可能是一个更好的主意
使用Event Aggregator意味着您有dispose订阅,这增加了工作量和复杂性。
Event Aggregator是您需要导入的依赖项,而CustomEvents是JavaScript的原生依赖
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