#Dependency injection preference

61 messages · Page 1 of 1 (latest)

sharp plume
#
private service = inject(Service);

or

public constructor(private service: Service) { }
stray ginkgo
#

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.

sharp plume
#

I find the first one to be more obvious for someone who would be new to dependency injection.

latent birch
#

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.

short mulch
#

The first one allows not repeating dependencies when using inheritance

That's the main selling point for me. First one for me.

spring light
#

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.

boreal glacier
#

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...

▶ Play video
spring light
# boreal glacier have you seen the latest video of Joshua Morony? https://www.youtube.com/watch...

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.

boreal glacier
spring light
#

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.

spring light
boreal glacier
#

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

spring light
#

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.

spring light
#

I can see for consistency, it now gets pushed everywhere. But I am not a fan of that, given my rant above 😄

boreal glacier
short mulch
#

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.

spring light
#

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 😬

stray ginkgo
# spring light But most arguments in favor of inject seem to be about inheritance, which is exa...

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.

spring light
#

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)

stray ginkgo
#

I guess you could just do

private fb = inject(NonNullableFormBuilder);
form1 = this.fb.group({ ... });
form2 = this.fb.group({ ... });
thorny siren
#

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

thorny siren
#

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)

stray ginkgo
#

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.

thorny siren
#

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)

stray ginkgo
#

Yes, it's a single project, using Karma / Jasmine. Monoliths FTW!