Related to but not the same as Difference between Constructor and ngOnInit.
In Angular 16 and 17 we can now use the takeUntilDestroyed
operator and Signals. Both of these seems to work best in an Injector Context or at least has an advantage that you do not need to pass one.
Question
So the question is (again) should we put initialization in the constructor (or member fields) or still use OnInit
? Second are there any pitfalls in using the constructor rather than OnInit
?
Note: With initialization I mean using httpClient
to fetch data to display on the page. Setup RxJS pipes with mapping of data, etc. Reading Route params, etc.
Set aside the following:
- We need to use
OnInit
orOnChanges
if we want to use@Input()
variables - Personal preference
Additional information
According to the old Angular.io site's Component Lifecycle documentation:
Components should be cheap and safe to construct. You should not, for example, fetch data in a component constructor. You shouldn't worry that a new component will try to contact a remote server when created under test or before you decide to display it. An ngOnInit() is a good place for a component to fetch its initial data.
But this documentation does not exist in the new Angular.dev site.
Also one of their new tutorials has data calls in the constructor:
constructor() {
this.housingService.getAllHousingLocations().then((housingLocationList: HousingLocation[]) => {
this.housingLocationList = housingLocationList;
this.filteredLocationList = housingLocationList;
});
}
Summary
It seems Angular 16/17 is going in a direction where more initialization is done in an injection context (member field or constructor). Does that have implications on performance, stability, future development?
It is better to initialize things in the member fields declaration. This way you can declare fields as
readonly
and you'll declare their type in the same line, and often it can be just inferred.If some of the fields are initialized in the constructor (not in the list of arguments), then you'll need 2 lines - one to declare the field (and its type), and one to initialize it:
Also, if some of the fields are initialized in the constructor, you might have a situation when you use fields before they are initialized:
No matter where
emails
is located, it will be initialized before theconstructor()
, and you'll get an error. And in reality code is not that short and simple, so it is quite easy to get this situation.If fields are initialized in
ngOnInit()
, you will not be able to declare them asreadonly
, even if they can be readonly (and it's quite a useful safeguard).ngOnInit()
andngOnChanges()
are only needed if you need to read multipleInput()
properties at once and execute logic, based on multiple of them. Although, I would recommend use setters andcomputed()
:With signal inputs, this code will be 6 lines shorter.
In tests, it is resolved with mocks.
|async
pipe and@defer
resolve the second issue. Also, if you want to convert some observable into a signal, and don't want to subscribe until that part of the template is visible, there is a helper function in the NG Extension library:toLazySignal()
.