#Dependency injection preference
61 messages · Page 1 of 1 (latest)
Some prefer the first one, some prefer the second one.
The second one makes dependencies obvious, but the dependencies must be repeated in each constructor when using inheritance.
The first one allows not repeating dependencies when using inheritance, but makes them less obvious, and can lead to errors if you refactor the code and end up calling inject out of an injection context.
I find the first one to be more obvious for someone who would be new to dependency injection.
IMO, the first is more obvious that Service is being injected, when reading through the source code. However, the second is more obvious that Service is required, when trying to construct the component manually (such as in unit tests). If the consumer forgets to provide Service, then the first results in a run-time error, while the second results in a compile-time error (which I find far faster to debug).
That said, it's fairly rare that you'd ever directly instantiate a component, even in a unit test. Most people use TestBed for that sort of thing, which would produce a run-time error with both syntaxes.
The first one allows not repeating dependencies when using inheritance
That's the main selling point for me. First one for me.
But that does hide it in the parent class, making u need to open the base class to know what its dependencies are. Definetly is personal, but not repeating the dependencies is exactly one of the reasons i, personally, dislike it.
Personally i would look into avoiding inheritance if you have it that much that this is the main selling point for inject.
Again, personal ofcourse. But you barely need inheritance. I cant even remember when i last used it in angular.
I believe inject is needed in a more functional world, think guards and interceptors. Class based, i see way more benefits to constructor injection.
But in the end, it's about personal preferences to some degree.
have you seen the latest video of Joshua Morony? https://www.youtube.com/watch?v=_quyWq4NnRM
Angular 14 gave us the ability to use the inject() function more generally in our projects, including replacing constructor based dependency injection. But is it a good idea to switch to it?
More on Dependency Injection: https://youtu.be/ZzC-hfYQXvQ
Get weekly content and tips exclusive to my newsletter: https://mobirony.ck.page/4a331b9076
L...
He mentions at 1.20 he likes it because it is clear what happens (something is being injected). Which entirely gets invalidated in the rest of the video when talking about inheritance and hiding all of it in the base, which requires people to dive into the base to start understanding that again. I would argue repeating constructor injection ensures this keeps visible and clear at all level of the inheritance chain.
Imagine having 3 levels of inheritance, all having their own inject stuff. One could say that's convenient, but arguably it's a bad idea as at the top level you lose track of all things being injected. You can say that's bad design of the inheritance to begin with, I agree. But inject allows for that.
What I also find weird is that the issues being called out with inheritance and constructor injection are how DI works in any class-based language (Java, .NET etc). Whereas inject is Angular-specific. I get why we need inject in some cases tho, as mentioned in a functional world we can't realy use constructor injection.
There are a lot of comments on the video I agree with tho, e.g.
Considering many other DI frameworks tried similiar techniques to the inject function and backed off it again (e.g. Spring Boot @Autowired which is essentially the same thing) and are encouraging constructor injection again.
But yeah, I am also one of those that believe the Service Locator pattern, which I believe inject comes very close to, is an anti pattern and would always avoid it when possible.
Imagine having 3 levels of inheritance, all having their own inject stuff. One could say that's convenient, but arguably it's a bad idea as at the top level you lose track of all things being injected. You can say that's bad design of the inheritance to begin with, I agree. But inject allows for that.
This is also why I believe inject shines in functional worlds, not in class-based worlds.
yeah that's his opinion on the topic without digging to deep. I guess both approaches have advantages and disadvantages, you just need to be aware of them.
Personally, I dont see repeating myself with dependencies in the ctor to be explicit as a disadvantage.
Especially given the limited amount we should use inheritance, but even more so because that is how pretty much all languages do DI with inheritance.
I also believe we are moving towards a more functional world with angular (interceptors, guards etc), so i do see the usefullness of inject there. And I would see it with components if they ever become functional as well. But I see more downsides than benefits to using inject in classes atm.
true...
one of the biggest criticism of angular it's the steep learning curve. I guess the angular team is also trying to make it more intuitive with @Inject. But i am not sure if this is really the case since now there is confusion about which one to use
That as well as I dont believe hiding things makes things easier on the long run.
Hiding things is fine as long as everything just works.
I can see how mocking things in tests can become more complicated to try and find out what services need to be or can be mocked .
Sure we use TestBed, but we still have to find that FooService to mock.
So we need to potentially dig into the source code of some third party package before knowing what dependecies it uses in order to know what we should mock.
Now imagine that third party package uses another third party package ... 😄
I mean, I know we should probably mock the third party thing initially to begin with, but we all know we do not live in an ideal world.
I dont mind digging into source code, but I know a lot of people that do.
My guess is ... Since they are introducing functional guards and interceptors, we can not use constructor injection, so they had to provide an alternative.
I can see for consistency, it now gets pushed everywhere. But I am not a fan of that, given my rant above 😄
hey but that's a nice feature 🙂
Take either of the 2 away and I’d be happy with what’s left.
I think everyone agrees inheritance is bad, but to me not having to repeat myself to what I’m extending is a perk. Separation of concerns. Just because the extended class needs it for it’s business logic doesn’t mean I do. If I need a service again I’ll inject it again, and I’ll know it’s because my component needs it and not some class down the line. I like the “standalone” concept personally.
I like inject because I know exactly where to look for all my class variables and ngOnInit makes constructor irrelevant anyway.
That said, Frederik is probably right that they introduced it for the functional guards and interceptors. Maybe it’s moving us closer to functional-based, maybe it’s just a multi-paradigm split again.
I like inject because I know exactly where to look for all my class variables and ngOnInit makes constructor irrelevant anyway.
So you mean you prefer looking at multiple places over one place ? I find it contradicting with not repeating yourself. As now, to know all the class variables, you need to look at all levels of the inheritance.
if you extend from something, your class is both, not just the parent.
Anyway, not here to convince people not to use inject. Just have a strong opinion on it 😂
I think it's the kind of thing that makes things easy at first, but eventually not so much when inheritance is involved.
If we ignore inheritance, I would say I dont care so much which one to use.
But most arguments in favor of inject seem to be about inheritance, which is exactly what triggers me 😬
I don't like inject very much, but I found one reason (in addition to functional guards) to use it (maybe not now, but in the future). One of the things I do very often is the following:
form = this.fb.group({ ... });
constructor(private fb: NonNullableFormBuilder) {}
This has the advantage of having a typed, non-nullable form group, where the type is inferred by the compiler from the initialization.
But I just recently realized that this only works because TypeScript executes instructions in the wrong order (compared to the standard JavaScript class properties standard). So this now works only because the TypeScript compiler is configured not to respect the JavaScript standard ("useDefineForClassFields": false). If we start respecting the JavaScript standard, this falls apart because the form would be initialized before the fb property. inject allows fixing that:
form = inject(NonNullableFormBuilder).group({ ... });
Another reason why inject might be pushed in the future, maybe, is that JavaScript now has standardized decorators, but this standard doesn't support decorating constructor arguments. So @Inject() would be unusable with standard decorators.
I can see how that could help. Arguably the solution should be elsewhere, but yeah I get what you mean, thanks for sharing that!
I have little to no experience with typed forms, but I am curious how that works with multiple forms in one component
Would we need to inject it twice, or more depending on the amount of forms?
(I dont think that question has anything to do with inject vs ctor injection, but more with typed forms rly)
I guess you could just do
private fb = inject(NonNullableFormBuilder);
form1 = this.fb.group({ ... });
form2 = this.fb.group({ ... });
Regarding tests, I don't normally use TestBed and just mock what's passed to the constructors, which makes those tests run significantly faster than with TestBed
And you lose that option with inject since it's a big side-effect in the middle of your code
So it's fairly similar to having to mock Date 😄
(I don't really need TestBed even for components since I don't test anything happening in the template)
And with functional guards/matchers/etc it's pretty awkward with a TestBed too
I assume there's something like
it('works', withInject([...], () => {
});
somewhere in Angular though, haven't checked
Well yeah but that one is with the test bed 😄
aka 5 hours to run all tests
(although it's more the setup than the test bed itself)
You must have avoided the TestBed for too long. Not counting the build time in ng test, I run 4900 tests in 2 minutes. And most of the tests test components, almost all without stubbing child components, and they do test the templates.
Hm I tried it with v15 recently and it takes several minutes
But for those 4900 you have just one project I assume?
I think the "heavy" part is the jest angular preset
Which runs... hundreds of times because hundreds of projects
I checked not long ago by removing all tests and just keeping the setup part (so jest runs and there are no tests), and it took 7 minutes 🥲
Just tried now, just 1 component:
describe('Test component', () => {
beforeEach(() =>
TestBed.configureTestingModule({
imports: [],
declarations: [TestComponent],
})
);
it('can be instantiated', () => {
expect(() => TestBed.createComponent(TestComponent)).not.toThrow();
});
});
✓ can be instantiated (297 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 19.535 s
So the test itself is ok, but the setup.. 😄
(note: it's on a GH action, locally it's "just" 4 seconds)
Yes, it's a single project, using Karma / Jasmine. Monoliths FTW!