Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Vicky Ryder
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with: Vicky Ryder@fuzie )

Object Access: Bracket-Notation vs. Dot-Notation With TypeScript In Angular 2 RC 4

By Ben Nadel on

When I started learning Angular 2, I did so in the context of ES5. But, after much community push-back and some clear evidence that some of the code would be more readable with TypeScript, I switched over to this type-driven super-set of JavaScript. And, I actually enjoy it. But, I do find that I spend a decent amount of time "just" getting "working JavaScript" to validate properly in TypeScript. Just the other day, in fact, I was completely stumped by object-access validation. Thankfully, Brandon Roberts was online and available to save the day!


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

Most of the time, in TypeScript, objects have narrowly defined interfaces. Meaning, the properties and methods available on the objects are known at transpile time. But, some objects cannot conform to such constraints - some objects are dynamic and driven by things like Router state. As such, it's not unusual to see an object with a loosely defined, key-driven interface:

  • interface IData {
  • [ key: string ]: any;
  • }

Here, we're basically saying that this object is a collection of string-based keys that will reference values of any type. This is a plain-old JavaScript object; or a hash; or whatever you want to call it.

When dealing with these kinds of objects in vanilla JavaScript, we can usually use either one of two notations:

  • Dot-notation, ex: data.foo.
  • Bracket-notation, ex: data[ "foo" ]

For all intents and purposes, these two notations are functionality equivalent (although you sometimes have to use the bracket-notation). In TypeScript, however, these two variants are not the same. And, in fact, given the perviously defined interface, only one of them will validate.

To see this in action, I've created a simple Angular 2 component that populates a data structure and then tries to access the values contained within that data structure using both types of notation:

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • // Here, we're defining an object interface to contain a collection of values. This
  • // is nothing out of the ordinary and is, in fact, how we think about objects. According
  • // to the interface, however, the keys have to be strings.
  • // --
  • // NOTE: In TypeScript, object interfaces must be defined as having "string" or
  • // "number" keys.
  • interface IData {
  • [ key: string ]: any;
  • }
  •  
  • @Component({
  • selector: "my-app",
  • template:
  • `
  • <p>
  • <em>Check the console-logging</em>!
  • </p>
  • `
  • })
  • export class AppComponent {
  •  
  • private data: IData;
  •  
  • // I initialize the component.
  • constructor() {
  •  
  • // Populate our arbitrary collection of data.
  • this.data = {
  • foo: "bar",
  • hello: "world"
  • };
  •  
  • // Now, let's try to access the data that we just populated. In the first
  • // approach, we going to use bracket-notation (which clearly uses a string-key).
  • console.log( "[FOO]:", this.data[ "foo" ] );
  •  
  • // Next, let's try to access the data using dot-notation. This approach should
  • // be "functionally equivalent" as the previous approach.
  • // --
  • // CAUTION: While this is perfectly valid "JavaScript", TypeScript doesn't
  • // realize that "hello" is a "string" and raises a validation error.
  • console.log( "[HELLO]:", this.data.hello );
  •  
  • }
  •  
  • }

Notice that the first console.log() statement uses the bracket-notation and the second uses the dot-notation. And, when we run this code, we get the following console-output:


 
 
 

 
 Object notation and type-checking in TypeScript for certain object interfaces. 
 
 
 

As you can see, both console.log() statements work - the code is functional and logs to the console; but, the latter call, using dot-notation, causes a TypeScript validation error. Presumably because the key is not explicitly defined as a string; though, of course, we all know that this is how JavaScript works.

NOTE: This divergence is not generically true in TypeScript. Meaning, most of the time, you can use either type of access notation. This is only true for objects that are loosely defined as a set of string-based keys.

I really do like TypeScript; but, in my studies, TypeScript is not a first-class citizen - I'm learning Angular 2 and, as a byproduct, I'm learning enough TypeScript to get the job done. Unfortunately, this means that I sometimes spend an inordinate amount of type just trying to satisfy the type-checker. In this case, Brandon Roberts was able to show me that bracket-notation and dot-notation, while functionally equivalent in JavaScript, are not the same thing in TypeScript.




Reader Comments

Define the data as follow and be happy:
private data : IData | Object;

Or, if you are going to use it in alot of places, define IData as follow:

Export type IData = {
[ key: string ]: any;
} | Object;

Reply to this Comment

@Bigous,

Ah, interesting. I'm still getting my head wrapped around TypeScript and how to use annotations. This might work when I'm in control of the type declarations. But, in Angular 2, if I'm consuming something that's part of the core, unfortunately, I'm not sure I can get around this.

For example, in the Angular 2 router, I can access the params associated with a route segment like this:

this.activatedRoute.snapshot().params

... this "params" object, if you look in the bowels of the source code, is defined as:

export type Params = { [key: string]: any };

... so, in this case, I don't think there's anyway around it. I have to use the bracket-notation when accessing the "params" or I'll get TypeScript yelling at me.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.