Angular - Is it possible to perform a switch statement rather than multiple @Input()s?

Viewed 1136

I am passing an attribute into my Angular component in the form of:

<my-component myAttribute></my-component>

In order to change a variable within the component that determines the CSS for width & height.

I also have pre-defined attribute values established such that inputting:

<my-component large>

Will set the width and height variables to equal 100 (100px after processing).

However, I am curious as to whether I can write multiple @Input() criteria in a way that I will not have lots of separate @Input()s, perhaps using a switch statement that will do the same job?

I have attempted this a little, but only received a sea of red squigglies under the code.

my inputs

size = 50 // Default value

  @Input('small') set small(value) {
    this.size = !value ? 25 : this.size;
  }

  @Input('medium') set medium(value) {
    this.size = !value ? 50 : this.size;
  }

  @Input('large') set large(value) {
    this.size = !value ? 100 : this.size;
  }

  @Input('x-large') set xlarge(value) {
    this.size = !value ? 200 : this.size;
  }
3 Answers

There's a way, but it is ... unconventional. I'm only able to perform it, I can't tell you if you should use it or not. So try it at your own risk.

This method (that you can find working here) gets rid of inputs and use the root Element of your component :

export class HelloComponent {
  size = '120';

  attributes = {
    'x-small': 50,
    'small': 75,
    'medium': 100,
    'large': 125,
    'x-large': 150,
  };

  constructor(
    private el: ElementRef<HTMLElement>
  ) { }

  ngOnInit() {
    this.setSize();
  }

  private setSize() {
    const attr = Object.keys(this.attributes).forEach(attr => {
      const hasAttribute = this.el.nativeElement.hasAttribute(attr);
      this.size = hasAttribute && this.attributes[attr] || this.size;
    });
  }

  getSize() {
    const myStyle = {
      width: this.size + 'px',
      height: this.size + 'px'
    };
    return myStyle;
  }

}

What it does is basically trying to find an attribute listed in the attributes object, and if it finds it, it sets the value corresponding to that attribute.

EDIT When you create private functions, they are accessible only on the component itself (kind of). This means they can't produce side effects on other components and don't really have to be tested.

For instance you could test a size like this

it('should be 100px wide when setSize', () => {
  component['el'].nativeElement.setAttribute('medium', '');
  component.setSize();
  expect(component.size).toEqual(100);
});

It is called an implementation detail : it's kind of like I've been telling you before : your setSize function is now abstracted, you assume it works and you test that when you call it, your size is effectively changing.

This means you don't have to test the inside of the setSize function, only that it behaves accordingly !

But you have to test all possibilities to prevent side effects : for instance, what happens if the attribute isn't medium, but exxxtra-large ? This is a case that you need to test.

This should fasten up your tests !

Pass the input as an object instead of a number. Then you can intercept the value passed using either:

a setter (as per your current attempt) OR

https://angular.io/guide/component-interaction#intercept-input-property-changes-with-a-setter

ngOnChanges

https://angular.io/guide/component-interaction#intercept-input-property-changes-with-ngonchanges

Using a setter, assuming dimensions is an object passed in, you might do this:

private _width = 0;
private _height = 0;

@Input()
set dimensions(dimensions: any) {
  this._width = dimensions.width
  this._height = dimensions.height
  // could put a switch case here
}

get width(): string { return this._width; }
get height(): string { return this._height; }

You could control and calculate the size of the child component within the parent component and send an object with variable properties. Once you have calculated this you could receive this value via 1 single input, say size.

Parent Component

<my-component size="this.childSize"></my-component>

Child Component

 private _size: any;

 @Input('size') set size(value) {
   this._size = value;

   const property = Object.keys(this.size)[0]; // expecting only 1 property

     switch (property):
       case 'small':
         // do something
         console.log(this.size[property]);
         break;
       case 'large':
         // do something
         break;
       default:
        // do something
   }

 public get size(): any {
   return this._size;
 }

Of course, you should create your own type for childSize to indicate & restrict what properties could be there. Examples, of objects you could send..

{
 small: 15
}

{
 large: 35
}
Related