8000 Add information on how to use injection in native classes by spuxx-dev · Pull Request #1715 · ember-learn/guides-source · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add information on how to use injection in native classes #1715

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 20, 2022

Conversation

spuxx-dev
Copy link

The article on native classes should include information on how to access injection (or possibly other framework features). I provided an example and wrote about what's going on to the best of my abilities, but take it with a grain of salt. :)
@NullVoxPopuli

@NullVoxPopuli
Copy link
Contributor

I'm a big fan of this! 🎉

@jenweber
Copy link
Contributor

I think if we include this in the guides, we should find another place for it. The native classes in depth article is generically about classes, not strategies that are very specific to Ember.

@LeopoldHock can you tell me more about what types of problems/challenges are solved by using this injection approach?

I'd like to check with some framework team members before we include this in the guides. I'm not sure we want to encourage this pattern, even if it works and is supported. For the Guides, we try to stick to the "happy path" way that people who are new-ish to Ember should follow. Sometimes things like this are best as a personal blog post or could be added to the Ember Atlas.

@jenweber
Copy link
Contributor

@chancancode @snewcomer @wycats do you have a sense of whether this is a pattern we should teach in the guides?

Also, with the upcoming deprecations of implicit service injection, do we need to make changes to this page as well? https://guides.emberjs.com/release/applications/dependency-injection

Thanks for your guidance on this!

@NullVoxPopuli
8000 Copy link
Contributor
NullVoxPopuli commented Aug 26, 2021

Enabling custom injections is an absolute requirement for most apps, ime 😶

For example, I use regular / vanilla classes to have access to the owner via setOwner so that I can access services from native classes in these scenarios:

  • manager patterns (plain-old-javascript-class), used as "local" or "private" services that all DDAU around a few places, or provide via provider component
  • any time I:
    @cached
    get myThing() {
       let instance = new MyClass(this.args.something); // has access to services, is re-new'd when @something changes
       setOwner(instance, getOwner(this));
       
       return instance;
    }
  • local state managed outside of components
    • I do this often because sometimes behaviors are complicated, and I like to keep my components focused on either minimal state or framework-specific stuff
    • imagine an animation-handler class that has lots of animation / tweening logic that you'd also want to tie in to a ResizeObserver service

I use also use registerDestructor on all this stuff, too :D

@spuxx-dev
Copy link
Author
spuxx-dev commented Aug 27, 2021

Hey @jenweber!

As NullVoxPopuli said, I feel that injections in custom objects are quite essential. I've been trying to look it up on more than one occasion and the solution I've described here was finally explained to me on Discord. I felt that the natice class article was the only real location this could fit - and it was kinda the first spot I searched.

I understand, however, this could be behavior you guys don't want to encourage. The obvious downside with this very solution is having to be very careful about ownership in general and with where the behavior is being called from. Maybe there is a better solution?

If there is, the question remains: How to make native classes play nicely with the framework? This is a topic that the guides are currently missing information about, imo.

@jenweber
Copy link
Contributor

@LeopoldHock and @NullVoxPopuli thanks for the context. Can you describe why injection is an advantage over calling/creating a service directly?

@NullVoxPopuli
Copy link
Contributor
NullVoxPopuli commented Aug 27, 2021

Can you describe why injection is an advantage over calling/creating a service directly?

most commonly (when I use this type of pattern), a singleton is the wrong pattern.
I often want to have multiple contexts with the same shape. or I don't want the entire app to have direct access to my class.
This could be:

  • multiple websocket connections on a page (using a websocket manager class with injections available so it can interact with ember-data) -- maybe the websockets connect to different hosts (example: in emberclear -- in this scenario each websocket connects to the same host, but with different public/private key pairs)
  • multiple graphics rendering contexts, for 3d / svg rendering
  • multiple form - wrapper objects (imagine a changeset, but with super powers)
  • when I want to use derived data to manage my state, which, since services are singletons, they do not fit well into derived data flow (like in the @cached example) <- this is the main one for me, and kind of the parent category of all scenarios where I'd want to do this.

Potentially more obscure, is requiring the owner on an instance, because a decorator requires that the owner exists:


Using this pattern allows your state to get setup and torn down with derived data patterns, components, etc.
whereas if you use a service, you're stuck with either global state that sticks around forever, or you have to manually reset / setup that state, and reset / setup methods are counter to the direction that I see ember going it -- which is primarily driven by derived data (like in my @cached example) and leaning heavily into one-way-data-flow and autotracking.

For fun, I wanted to see how many occurrences of setOwner were presently in my work project:

❯ git grep "setOwner(" | wc -l
114

🤷

@jenweber
Copy link
Contributor
jenweber commented Sep 2, 2021

Just following up, I haven't heard from anyone on the framework team yet. I expect we may need to wait until after v4 is released to get their feedback, but I'll ask around now too.

item.addToCart();
```

Alternatively, you can call `setOwner` in the class constructor and simply supply the caller as an argument to the constructor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think setting it first thing in the constructor (before any of the injections were accessed) is the only form that is guaranteed to work. If the owner relationship is not established at initialization time things may behave unexpectedly and the wrong results may get cached.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is still fine for constructorless classes (which I have many of)

@NullVoxPopuli
Copy link
Contributor

anything keeping this from being merged? I'd like to see a list of blockers, otherwise let's move forward / merge?

@jenweber
Copy link
Contributor

The blocker on this PR was that I would like to hear from someone on the Framework Core Team to confirm that this content should be included. @chancancode does this seem ok to you?

(sorry everyone else for the ping, I'm doing some work on old PRs/loose threads this week!)

@chancancode
Copy link
Contributor

I think it's totally good to include it in the in-depth guide. I do still think we should either remove the setting-owner-after-construction pattern or at the very least deprioritize it. The general expectation is that the owner will be set by the time the class has been "constructed completely".

For framework classes this is generally enforced by making the owner a required constructor argument on the base class (and the owner is set for you there). Since JavaScript enforces that you must call super() very early in your constructor, this is usually enforced automatically.

For non-framework classes, I think it's still generally good to follow this pattern/expectation. I cannot personally guarantee (i.e. I haven't asked others who were more involved in designing this and re-reading the relevant RFCs) that it is considered "supported" to set the owner outside of the constructor before "touching" this. I believe the intention is to consider setting the owner conceptually part of the object allocation/initialization, we just don't really have tools to enforce it unlike JavaScript.

So if we just want to add the section on setting it in the constructor, which covers a lot of the use cases, I think it's a no-brainer. If it is for some reason very important to discuss/prioritize the other part of this PR then I am probably not the best person to review it.

@jenweber
Copy link
Contributor

Hello @LeopoldHock, thanks again for writing this! And thanks to our reviewers. I appreciate everyone's patience. This is a little outside my knowledge of Ember and I wanted to be sure before signing off on it. Merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
0