-1

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.

panda-byte
  • 463
  • 3
  • 10

1 Answers1

2

A recent addition to Angular v16 is the ability to mark component inputs as required. Here's an example from the linked post:

@Component({
  selector: 'app-foo',
  standalone: true,
  templateUrl: './foo.component.html',
})
export class FooComponent {
  @Input({ required: true }) elementId: string;
}

Alternatively, you can provide a placeholder value in the case that nothing is provided:

@Component({
  selector: 'app-foo',
  standalone: true,
  templateUrl: './foo.component.html',
})
export class FooComponent {
  @Input() elementId: string = 'myElementString';
}

But more broadly it might be wise to use the optional access syntax if you think there may be times when nothing is provided. You can use *ngIf checks and provide a default behavior. It definitely makes the component file more verbose, but it leads you to writing better, more defensive code that reduces the chance for errors.

Nick Felker
  • 11,536
  • 1
  • 21
  • 35
  • Thank you. I know about the 'required' parameter, but it doesn't really solve any of the described problems, because on a typing level I still need to deal with the possibility of the inputs being not initialized. I don't really agree with "better, more defensive" code, because leaving it up to the developer to deal with any invalid state just invites errors and leads to boilerplate code prone to copy&paste mistakes. I'm sure there could be a better way, Angular just doesn't offer it. – panda-byte Aug 21 '23 at 10:42