50

I'm trying to write a component to be reused across my application, which will sometimes show a control button and sometimes not.

Ideally, I'd want to get this from the presence or absence of an attribute, so that using the component would look something like <generic-component hideControls></generic-component>, with a boolean variable in my component based on whether that attribute is present, but I can't see a way to do this.

Is there a way to do this?

I have tried with the slightly more messy version below (although ideally I would not need to set a value on showControls/hideControls);

generic.component.html

<div>
  <div>Stuff</div>
  <div *ngIf="showControls">Controls</div>
</div>

generic.component.ts

// imports etc.
@Component({
  selector: 'generic-selector',
  templateUrl: 'generic.component.html'
})
export class GenericComponent implements OnInit {
  @Input()
  public showControls: boolean = true;

  // other inputs and logic
}

This fails, because the usage <generic-selector showControls="false"></generic-selector> sets showControls as the string "false", which is truthy, rather than as the boolean value. So I have to get round it by adding a lot of mess to the component to take in the input and convert to a boolean based on whether it is being given the string "false" or not. A non-messy way to sort this would be appreciated, but I'd prefer being able to do the other option above.

meta
  • 829
  • 1
  • 10
  • 23

4 Answers4

58

This fails, because the usage <generic-selector showControls="false"></generic-selector> sets showControls as the string "false", which is truthy, rather than as the boolean value. So I have to get round it by adding a lot of mess to the component to take in the input and convert to a boolean based on whether it is being given the string "false" or not. A non-messy way to sort this would be appreciated, but I'd prefer being able to do the other option above.

using binding

<generic-selector [showControls]="false"></generic-selector>
Tiep Phan
  • 12,386
  • 3
  • 38
  • 41
  • 7
    [showControls]="false" works well. showControls={{false}} does NOT work. – Maz T Apr 17 '18 at 11:59
  • Learned more about these bracket-attributes here https://stackoverflow.com/questions/35944749/what-is-the-difference-between-parentheses-brackets-and-asterisks-in-angular2 – SeanMC Aug 27 '18 at 01:08
  • solution working perfectly but why is angular not considering false as variable someone please explain... – Abhijit Jagtap Aug 09 '21 at 17:41
  • @AbhijitJagtap `false` is a reserved keyword in JavaScript and can't be used as a variable name. – andii1997 Aug 27 '21 at 08:54
39

You can use the Input decorator as you would normally do with other attributes. The only trick is that when the attribute is non-present, the value would be undefined, otherwise, the value would be an empty string.

Logic:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: '',
  templateUrl: './boolean-component.component.html'
})
export class AppBooleanComponent implements OnInit {

  @Input('boolean-attribute') booleanAttr: boolean;

  ngOnInit() {
    this.booleanAttr = this.booleanAttr !== undefined;
    console.log(`Boolean attribute is ${this.booleanAttr ? '' : 'non-'}present!`);
  }

}

Template 1 (logs 'Boolean attribute is present!'):

<app-boolean-component boolean-attribute></app-boolean-component>

Template 2 (logs 'Boolean attribute is non-present!'):

<app-boolean-component></app-boolean-component>
Ramtin Soltani
  • 2,650
  • 3
  • 24
  • 45
16

In Angular there are two ways to assign a value to your property:

  1. using as HTML attribute
  2. with square brackets

In the first case, the value of your property will always be a string, like every HTML attribute even if you'll assign an interpolable string like this: {{true}}.

While in the second case, it'll be interpreted as a JavaScript expression but in your context. So if you'll have an object notation there the value of your attribute will be the parsed as an object. But for specifying context angular is not using "with" statement so you can't use your globals there, but only the properties of the component in which scope your component is inserted.

<generic-selector [showControls]="false"></generic-selector>

In this case, it takes the string "false" and translates it to JavaScript. So in JavaScript, it's a boolean, then you'll get it as a boolean.

But if you'll have something like this: <generic-selector [showControls]="{myProp: 'val'}"></generic-selector>

then the type of your showControls will be object and its value will have myProp property equal to 'val'.

but if you'll have some nonliteral there then it'll be considered to be a property of your class:

<generic-selector [showControls]="location"></generic-selector>

then if you'll have a location property on the scope of the component containing your generic-selector then the value of its location property will be taken otherwise it'll be undefined.

I'll suggest you to consider using ngOnInit for logging the property's type and the value assigned.

hakobpogh
  • 632
  • 6
  • 13
1

With Angular 16.1 or later you can use the transform option with the booleanAttribute function on the @Input() and let Angular handle the transformation of the input to a boolean:

import {Component, Input, booleanAttribute} from '@angular/core';

@Component({
  selector: 'generic-selector',
  templateUrl: 'generic.component.html'
})
export class GenericComponent implements OnInit {
  @Input({transform: booleanAttribute}) showControls = false;

  // other inputs and logic
}

For more details see the pull request which added the feature.

JSON Derulo
  • 9,780
  • 7
  • 39
  • 56