I'm working on a web application that uses Angular (v16) as a framework.
I have some issues with the way Angular initializes data for components. I often define components which require certain data as inputs, and without it there's no use displaying anything. In many cases, this data is fetched asynchronously. I imagine this use case to be very common and to be used basically all the time.
However, it is quite awkward to achieve this with Angular. Let's say I have a PersonComponent
component which should display some details about a person (represented by a Person
object).
export interface Person {
name: string;
birthYear: number;
}
@Component({
selector: 'app-person',
template: '<p>{{person?.name}} age: calcAge()<p>',
})
export class PersonComponent {
@Input()
person: Person | undefined;
public function calcAge(): number | undefined {
if (this.person === undefined) {
return undefined;
}
return ...
}
It's impossible to setup a contract for a component where the component can rely on a valid state after initialization with regard to inputs. I'm forced to declare the person
's type as Person | undefined
instead of just Person
, because in the moment the component is constructed the value isn't set. If I instead choose some other default value of the same type, this can lead to problems, and it's even harder to detect if an input variable is initialized properly. But with the possibility of person
being undefined, it creates a whole other slew of problems. Basically, I have to check at every turn if my input is properly initialized. In templates, I need to use optional chaining, nullish coalescing or wrapping the whole template inside an ngIf
block which asserts that the inputs are defined. In the component logic, I need to precede every function with an additional check, and then those functions also might return undefined. When I use the component inside another template, I also need to wrap it inside an ngIf
block if I want to make sure nothing is instantiated without properly initialized data.
The problems are only exacerbated when there are multiple required inputs, or when using asychronous data (where the async
syntax already feels very wonky). Is there some better way to handle this sort of situation in Angular? I feel like it takes a lot of workarounds just to make sure the component is in a valid state and has all the data it needs. I understand that in other cases, components load different sorts of data on their own or need to display something even in basically non-initialized state. But more often than not I have the situation where I either have some data available or not, and I would like to display the corresponding data (if available) with a component, and otherwise not show anything at all. In that case, it's surprisingly cumbersome to prevent invalid object state.