Alternative
Closed this issue ยท 10 comments
If it is of any interest... I did similar things with sub-events, as I explained within the Safety notes.
A very basic EventConsumer
class hides the event, so clients can only subscribe, while the provider can do anything it wants, including emitting events.
Hi, @vitaly-t! Thanks for sharing! Nice idea!
There were some reasons for me to don't expose anything else other than the TypedEventDispatcher class in this lib. But could you explain a little more about how you thought it could be used? I might be missing some detail.
could you explain a little more about how you thought it could be used
But I gave you the link with example of how exactly this can be used ๐ It just encapsulates the event object, exposing only the methods considered safe, that's all. The effect of it is the same as having a separate TypedEventDispatcher
.
If you mean something in particular, just ask ;)
Oh yeah, I've read the two links :)
And my first guess was that you were suggesting it because I'm returning an object as the getter()
of this lib, like this:
this.typedEvent = {
addListener: this.addListener,
removeListener: this.removeListener
};
Object.defineProperties(this.typedEvent, {
listeners: { value: this.listeners },
oneTimeListeners: { value: this.oneTimeListeners }
});
while I could be returning an instance of an EventConsumer
, like on your article.
Or I could also just export an EventConsumer
, so users could by themselves create the "Event getter" wherever they needed.
Is it correct?
I had an implementation like that on this previous release: my lib was exporting two classes: TypedEventDispatcher
and TypedEvent
(which, I believe, was similar to the EventConsumer
). But I changed it to export only one class to avoid people trying to instantiate the TypedEvent
by mistake (even it was already marked as an abstract class). I thought it would grant a more straight forward usability.
Also, another reason for having exported it as an object like now was to avoid the TypedEvent objects having the listeners
and oneTimeListeners
properties exposed, after compilation, on the JavaScript-side.
Please, let me what you think about that :)
In my library, I went for what I thought was the simplest approach - one class for emitting and consuming events. My thinking was that if you suddenly do need it separate, you can easily implement an event consumer like that, so no need to over-engineer things from start.
to avoid the TypedEvent objects having the listenersand oneTimeListeners properties exposed, after compilation
I'm not not really sure how that can be, if you use TypeScript and declare those as private. This is how listeners are done in my library.
In my library, I went for what I thought was the simplest approach - one class for emitting and consuming events. My thinking was that if you suddenly do need it separate, you can easily implement an event consumer like that, so no need to over-engineer things from start.
Yeah! That's what I said was a nice idea :)
But for now, I don't intend to give a second-way to consume the events, other than using <dispatcher>.getter
. It was intentional to give only one way of getting the consumer.
I'm not not really sure how that can be, if you use TypeScript and declare those as private. This is how listeners are done in my library.
That's true, when using Typescript or Javascript files with .d.ts linked.
But, even so, when checking the properties of those objects at run time, we can see and mutate those properties marked as private on the class. To avoid it, I've taken some measures, so on the current version of this lib, if we print the properties of the objects, we see this for the eventDispatcher
object:
const eventDispatcher = new TypedEventDispatcher();
console.log(eventDispatcher);
console.log(Object.keys(eventDispatcher));
console.log(Object.getOwnPropertyNames(eventDispatcher));
TypedEventDispatcher {
getter: {...},
dispatch: {...}
}
["getter", "dispatch"]
["getter", "dispatch"]
And this for the eventDispatcher.getter
object:
const eventDispatcher = new TypedEventDispatcher();
console.log(eventDispatcher.getter);
console.log(Object.keys(eventDispatcher.getter));
console.log(Object.getOwnPropertyNames(eventDispatcher.getter));
{
addListener:f addListener {...},
removeListener:f removeListener {...}
}
["addListener", "removeListener"]
["addListener", "removeListener"]
I'm closing the issue for now, @vitaly-t. Thank you for sharing your thoughts and helping to improve this lib!
@felladrin Sorry for the late reply...
when checking the properties of those objects at run time, we can see and mutate those properties marked as private on the class
We can't, if you implement those right. Within the classic TypeScript that means using WeakMap
, while in the latest TypeScript, when you are using the new #prop
syntax, TypeScript can automatically emit WeakMap
.
And WeakMap
gives you the actual private properties. Here's one example in my library where I used it, in order to support receive-only event clients.
I also outlined the particulars within the safety notes.
Wow! I knew the WeakMap approach, but the private properties starting with #
is completely new to me! Looking into it right now. Thanks!
Here's generic support for a set of properly private properties I did:
/**
* Implements proper private properties.
*/
class Private<K extends object, T> {
private propMap = new WeakMap<K, T>();
register(obj: K, val: T) {
this.propMap.set(obj, val);
}
get(obj: K): T {
return this.propMap.get(obj);
}
}
Then in some other file, you have class TestClient
that needs private properties:
// Private-Properties interface:
interface TestClientPrivate {
prop1: string;
prop2: number;
}
// private properties for every TestClient instance:
const pp = new Private<TestClient, TestClientPrivate>();
class TestClient {
constructor() {
pp.register(this, {
prop1: 'hello',
prop2: 123
});
}
someMethod() {
const privateProps = pp.get(this);
const prop1 = privateProps.prop1;
const prop2 = privateProps.prop2;
}
}
It gets even simpler, if you have only one private property, then you do not need to define a type like TestClientPrivate
.