Repository navigation
Deprecate Observers - #1115
Deprecate Observers#1115
Conversation
bd08ea3 to
bdf236c
Compare
|
RFC review discussion notes:
|
739e672 to
7a1af9f
Compare
|
Hi @wagenet . We have a use case where external services sent a realtime notification which our app listens via observer and runs a callback. I think this is a very standard use case. Would be glad to know how to migrate such use cases where the trigger happens outside the app |
|
@ganapathyHf can you share more about your use case, and maybe a specific code example? For truely evented behavior, we have The easiest (and most controlled) is passing a function to an external library |
|
@NullVoxPopuli Sure Here is an example flow of how we handle realtime notifications The realtime service owns the WebSocket connection. When a socket event is received, it normalizes the payload and assigns it to a notification property: Different parts of the application observe this property and react when it changes. For example, the Knowledge Base area observes realtime notifications because its metadata can be changed by another user: The benefit was the web socket implementation remains centralized in the realtime service, while individual features decide which notifications are relevant to them. Our app has a mix of both classic and Octane patterns. So in case of Octane, currently we follow a workaround where we register the listener as computed decorator And to invoke this, we mention them in the corresponding template As I mentioned this is a workaround and very implicit. Hope the examples are clear. I would like to know what is the preferred approach to migrate off from both of these patterns? |
|
thanks for the examples! Here is the minimal change way how I'd do the notification thing across the whole app, since: i see:
// app/services/real-time-service.js
export default class RealTimeService extends Service {
@tracked lastNotification;
}
// passing the real time service somewhere
// you didn't show this part in your examples, but I assume it's something like this.
// there are many ways to pass data/objects/etc to external things
class X {
@service realTimeService;
setup() {
// MyExternalThingNotFromEmber
// can still do realTimeService.lastNotification = newNotification
this.#external = new MyExternalThingNotFromEmber(this.realTimeService);
}
}and then for your in app-usage of handling changes to lastNotification -- you still want to use data derivation: class X {
@service realTimeService;
get isNotificationRelevant() {
return this.realTimeService.lastNotification.includes('something');
}
// it's important that this is cached, else we'll unneededly
// trigger multiple fetches when tracked data has not changed
@cached
get request() {
if (!this.isNotificationRelevant) return;
return fetch(...)
}
// getPromiseState here can be from warp-drive, or reactiveweb
// it's a good general-purpose utility for telling ember about promises
// - https://reactive.nullvoxpopuli.com/functions/get-promise-state.getPromiseState.html
// - https://warp-drive.io/api/@warp-drive/core/reactive/functions/getPromiseState#getpromisestate
//
// See Also:
// - https://github.com/emberjs/rfcs/pull/1060
get state() {
return getPromiseState(this.request);
}
}And for your second example, the most direct way to do that would be to: class X {
@service realTimeService;
doSomething = () => {
let notification = this.realTimeService.lastNotification
}
<template>
{{ (this.doSomething) }}
...
</template>
}This has caveats though, as this is how you do an "effect" in ember, and like with all effects in all frameworks, they have the same caveats -- in that if not careful, you can cause infinite loops (which are hard to detect, due to how the timing of your effects work). That said, this is a common enough pattern, where I made a utility to try to make this as safe as possible here: The code is very simple (most of this is just documenting the pattern): Now, something that I think could be a fun little plain JS, don't worry about what ember is doing way of handling your observer case is to make your own event system -- which is actually very few lines -- for example: at the consumer side: // ..
@service realTimeService;
this.realTimeService.onNotification(() => { /*....*/ })
// in real-time-service.js
export default class RealTimeService extends Service {
/**
* no one is allowed to set this directly
* Using tracked like this requires ember 7.3+
*/
#lastNotification = tracked(null);
#callbacks = [];
onNotification = (callback) => {
this.#callbacks.push(callback);
}
// consumers now use this
setNotification = (newNotification) => {
this.#lastNotification.value = newNotification;
this.#flushSubscribers();
}
#flushSubscribers() {
let lastNotification = this.#lastNotification.value;
for (let callback of this.#callbacks) {
callback(lastNotification);
}
}
} |
|
@NullVoxPopuli I am going through the examples. Will let you know if i have any doubts. Wanted to mention that we are currently migrating from 3.28 to v4 so i think usage of |
|
template tag works on ember 3.28 via https://github.com/ember-cli/ember-template-imports
since you're on 3.28, going to 4, the only thing from my examples that won't work is the |
Propose Deprecating Observers
Rendered
Summary
This pull request is proposing a new RFC.
To succeed, it will need to pass into the Exploring Stage, followed by the Accepted Stage.
A Proposed or Exploring RFC may also move to the Closed Stage if it is withdrawn by the author or if it is rejected by the Ember team. This requires an "FCP to Close" period.
An FCP is required before merging this PR to advance to Accepted.
Upon merging this PR, automation will open a draft PR for this RFC to move to the Ready for Released Stage.
Exploring Stage Description
This stage is entered when the Ember team believes the concept described in the RFC should be pursued, but the RFC may still need some more work, discussion, answers to open questions, and/or a champion before it can move to the next stage.
An RFC is moved into Exploring with consensus of the relevant teams. The relevant team expects to spend time helping to refine the proposal. The RFC remains a PR and will have an
Exploringlabel applied.An Exploring RFC that is successfully completed can move to Accepted with an FCP is required as in the existing process. It may also be moved to Closed with an FCP.
Accepted Stage Description
To move into the "accepted stage" the RFC must have complete prose and have successfully passed through an "FCP to Accept" period in which the community has weighed in and consensus has been achieved on the direction. The relevant teams believe that the proposal is well-specified and ready for implementation. The RFC has a champion within one of the relevant teams.
If there are unanswered questions, we have outlined them and expect that they will be answered before Ready for Release.
When the RFC is accepted, the PR will be merged, and automation will open a new PR to move the RFC to the Ready for Release stage. That PR should be used to track implementation progress and gain consensus to move to the next stage.
Checklist to move to Exploring
S-Proposedis removed from the PR and the labelS-Exploringis added.Checklist to move to Accepted
Final Comment Periodlabel has been added to start the FCP