Handling Multiple @Input Fields with Angular Components

When building an Angular component, whether using Dart or TypeScript, sometimes it can be difficult to know how to handle multiple required inputs. The problem arises because, from the perspective of a reusable component, it can be difficult to predict when and in what order the values will arrive.

Note: The examples here will use the Dart language, but the principles apply equally to TypeScript projects. Also note that this code supposes you're using Angular 2+ or AngularDart 2.0+.

Strategy 1

Suppose you have a widget component requiring two pieces of data to initialize and show its view:

<my-widget [required1]="data1" [required2]="data2"></my-widget>

The widget does not know how it will be used. Will required1 be assigned a literal right there in the template, or will it rely on data binding to update the value at some unknown time? If the widget attempts to do its thing with either or both values missing, catastrophe! Furthermore, it'd be great if the component could react to changes for either or both of the inputs.

So how do you handle this within the widget code?

import 'package:angular2/core.dart';

@Component(
  selector: 'my-widget'
)
class MyWidget {
  String _required1;
  String _required2;

  void _doMyThing() {
    if (_required1 == null || _required2 == null) {
      return;
    }

    print("I'm doing it with $_required1 and $_required2!");
  }

  @Input()
  void set required1(String value) {
    if (_required1 != value) {
      _required1 = value;
      _doMyThing();
    }
  }

  @Input()
  void set required2(String value) {
    if (_required2 != value) {
      _required2 = value;
      _doMyThing();
    }
  }
}

Every time a new input value becomes available to the widget, the appropriate setter will record the value and attempt to _doMyThing(). For its part, _doMyThing() makes sure it has all required values before it really does its thing. Meanwhile, the setters never bother to restart the whole show if the incoming value is the same as what the widget already has. Incidentally, this protection also helps prevent early null values from the data binding system triggering the action without a valid value, since uninitialized variables in Dart start life as null.

Strategy 2

But what if there are 3, 4, or even more required pieces of data required for the widget to do its thing? You could keep expanding Strategy 1 forever, if you want, but that code would start to get messy. In these cases, it might be better to use a configuration object:

class WidgetConfig {
  String required1;
  String required2;
  String required3;

  bool get isValid =>
      required1 != null && required2 != null && required3 != null;
}

Now, your widget needs to accept one of these things as an input:

<my-widget [config]="widgetConfig"></my-widget>

And the new widget class:

import 'package:angular2/core.dart';
import 'widget_config.dart'

@Component(
  selector: 'my-widget'
)
class MyWidget {
  WidgetConfig _config;

  void _doMyThing() {
    if (_config.isValid) {
      print("I'm doing it with $_required1 and $_required2!");
    }  
  }

  @Input()
  void set config(WidgetConfig value) {
    if (_config != value) {
      _config = value;
      _doMyThing();
    }
  }
}

In this version, a user of the widget needs to construct a new instance of WidgetConfig each time he wants the widget to do its thing. Validation is handled neatly in the config object and could be as simple or complex as you like without getting in the widget's face.

Until next time!