101

I'm learning Angular2. I have a component with a variable which is an object. I'm iterating over the fields of the object, and acording to the type of data of that position, I need to render a different compoenent. In this case, I want tu render that label if the typeof that position is a number how ever this is not working

<div>
  <div *ngIf='obj'>
    <label *ngFor="let key of keys; let i = index">
      <label class='key'>{{key}}:</label>
      <label class='number' *ngIf='typeof obj[key] === "number"'>
      <!-- label class='number' *ngIf='obj[key] | typeof === "number"' -->
        {{ obj[key] }}
      </label>
    </label>
  </div>
</div>

Any ideas?

I have also created a pipe to get the typeof which work when I print the value, but not inside the *ngIf

Pablo
  • 9,424
  • 17
  • 55
  • 78
  • See section _Template expressions_, and in particular, section _Expression context_ in the [Template Syntax dev guide](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#template-expressions). – Mark Rajcok Jun 03 '16 at 02:34

6 Answers6

136

Globals like window, typeof, enums, or static methods are not available within a template. Only members of the component class and typescript language constructs are available.

You can add a helper method to your component like

isNumber(val): boolean { return typeof val === 'number'; }

and use it like

<label class='number' *ngIf='isNumber(obj[key])'>
andy
  • 129
  • 3
  • 12
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    thanks! I have created this method which should return the same as `typeof` in my class to solve! toType (obj) { return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase(); } – Pablo May 29 '16 at 14:40
  • What if I have many types and want to use an ngSwitch. Is it possible? – mdarefull Apr 07 '18 at 09:09
  • It's plannec, but not yet implemented. See https://github.com/Microsoft/TypeScript/issues/2214. The comments show some workarounds – Günter Zöchbauer Apr 07 '18 at 09:16
  • 18
    Just a note that this isn't advised for performance reasons, as the function will run on every digest. – jarodsmk Sep 05 '18 at 11:29
  • @N15M0_jk you are right, that applies to every getter and functions accessed from bindings. It's always better for performance to assign the result to a field and bind to that field instead. That might not always be necessary. If you use OnPush it's less of an issue, because then change detection is executed much less frequently. – Günter Zöchbauer Sep 05 '18 at 11:33
  • 1
    @GünterZöchbauer I came across some of the other SO posts about it and thought of this one too, since it wasn't mentioned here. Will add another answer when I get a chance just for completeness – jarodsmk Sep 06 '18 at 06:56
  • i remember angularjs had issue with **automatic** updating digest and scope, when you used a function for things such as ng-if... is it solved with Angular X.. or it still prefer to call functions only once? – Hassan Faghihi Apr 08 '19 at 06:24
  • 2
    @deadManN These digest issues are completely gone. Change detection is implemented completely different to AngularJS.There are some edge cases like where a value is changed and then changed back to the original value (like true -> false -> true) where Angular does not recognize a change when change detection is not invoked manually after `false`. This can cause issues in rare cases like a checkbox where the update event handler revokes the change. There are also some rare cases that are not covered by NgZone automatically and need `NgZone.run(...)` to be forced into Angulars zone. – Günter Zöchbauer Apr 08 '19 at 06:43
  • @deadManN I think this is no true, if I print something to the console from the "helper" method, it's creating 1000 log records every second. Even in Angular 10. – Peter Feb 03 '21 at 16:55
  • 1
    @Peter you need to be careful. If you return sm object it must be the same instance if the content (property values) haven't changed. That's not an issue with primitive values like string, number, boolean – Günter Zöchbauer Feb 03 '21 at 19:01
  • @Peter you need to be careful. If you return an object, it must be the same instance if the content (property values) hasn't changed. That's not an issue with primitive values like string, number, boolean – Günter Zöchbauer Feb 03 '21 at 19:01
  • 1
    @Peter unfortunately, to be honest its long since I have worked with AngularJS, and so I forgot everything about this matter. – Hassan Faghihi Feb 06 '21 at 06:47
59

You can create simple pipe which will receive current item and return item type.

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'typeof'
})
export class TypeofPipe implements PipeTransform {

  transform(value: any): any {
    console.log("Pipe works ", typeof value);
    return typeof value;
  }

}

Now you can use typeof pipe in html like this

*ngIf = (item | typeof) === 'number'

And be careful in using function call in your html as mentioned above. It preferred to use pipe instead of function call. Here is Stackblitz example. In first case function call will triggered on any change detection (example: clicking on buttons).

Armen Stepanyan
  • 1,618
  • 13
  • 29
  • You might find this usefull, it allows you to cast using a pipe - https://stackoverflow.com/a/66154034/1990451 – Mauricio Gracia Gutierrez Jun 24 '21 at 03:56
  • 1
    Not sure why this isn't the top answer. If you'd like it to check for arrays too, use ` if (value instanceof Array) { return 'array' } else { return typeof value; } ` – Collin May 11 '22 at 17:32
20

Alternatively, you can compare the constructor name.

{{ foo.constructor.name === 'FooClass' }}

Detailed info about this here.

zurfyx
  • 31,043
  • 20
  • 111
  • 145
  • 1
    This won't work, for obvious reasons, in cases where you need to check that something is, in fact, defined. `(typeof thing !== 'undefined)`. Might be worth mentioning in the answer, might save others from feeling stupid for a few seconds for not immediately considering this; even a few years after this was posted. – Cârnăciov Mar 13 '19 at 14:23
  • 1
    @aron9forever If there's a chance that it might be `undefined`, add a safe navigation operator: `foo?.constructor`. – tom May 03 '19 at 23:51
  • 1
    Important: See Gabe Martin-Dempesy's comment under the [linked answer](https://stackoverflow.com/a/332429/2013580) why this is a bad practice for Angular and won't work in production. – tom May 05 '19 at 21:13
  • Also, you might be checking against a base class type, in which case this again won't work. – Mark Feldman May 18 '21 at 23:53
  • This also won't work in a minified production version. – seawave_23 Jun 21 '21 at 12:18
  • This *almost* worked for my use case, and apparently (hard to say for sure) OP's use case, which is just checking against built in data types like Number, Array, String. I think it should hold up when minified too, since the definitions of those types should be outside your project, and therefore would not be getting minified. The problem I ran into was with Angular1.5 not allowing the safe navigation operator. Maybe versions 2+ allow it tho. – maurice Jan 19 '23 at 21:59
8

I just tried this and found it won't work in production because function names are shortened. It's safer to use something like:

foo instanceof FooClass

But note that you have to do this in the component/directive because instanceOf is not available in templating:

// In your component
isFoo(candidate){
    return candidate instanceof FooClass;
}

// in your template
{{isFoo(maybeFoo)}}
Saber
  • 5,150
  • 4
  • 31
  • 43
Owen
  • 121
  • 3
  • 5
1

This is a bit of a hack, but if you need to do this in a lot of places and don't want the cruft of passing some isNumber function around, there's another option that can work if you use it carefully.

You can check for the existence of properties or methods that exist on the prototype of the object or type you're looking for. For example, all numbers have a toExponential function, so:

<label class='number' *ngIf='obj[key] && obj[key].toExponential'>

For functions you could look for call, for strings you could look for toLowerCase, for arrays you could look for concat, etc.

This approach isn't foolproof at all, since you could have an object that happens to possess a property with the same name that you're checking (though if the property you're checking is all you need, then we're basically duck typing), but if you know that the value you have is a primitive you're in good shape, since you can't assign properties on primitives (here is some interesting reading on that topic).

Disclaimer: I don't really trust that this is a good idea and may not be very maintainable or portable, but if you just need something quick for a prototype or a very limited use case, this is a reasonable tool to have in your belt.

tobek
  • 4,349
  • 3
  • 32
  • 41
0

You can simply try this

In your TS set a variable

isNumber = isNaN;

In your template

<p>{{isNumber(your value) ? 'do something' : 'else' }}</p>
  • You'd need to invert that statement, since isNumber('...') would return the opposite (as it's checking if it's not a number) – Boian Ivanov Feb 07 '23 at 14:30