9

Just new to typescript in an angular context. Can you mark properties as optional in the example below? How would you recommend specifying a nav item has no children? With regular JS I would usually rely on a falsey value to check if the object has children property. But in Typescript is it best practice to initialise an empty array? What about other primitive properties that may/may not have values?

import { Injectable } from '@angular/core';

export class NavItem {
  key: string;
  text: string;
  target: string;
  children: NavItem[];
}

const data: NavItem[] = [
  {
    key: "dashboard",
    target: "dashboard",
    text: "Dashboard",
    children: []
  },
  {
    key: "sales",
    target: "sales",
    text: "Sales"
  }
];

@Injectable()
export class NavService {
  getNav() {
    return data;
  }

}

1 Answer 1

20

Yeah, it's very easy to mark a property as optional, you add ? after it:

class NavItem {
  key: string;
  text: string;
  target: string;
  children?: NavItem[];
}

Alternatively you can also use a union with null or undefined:

class NavItem {
  key: string;
  text: string;
  target: string;
  children: NavItem[] | undefined;
}

With that being said, you misunderstood something very important, this is wrong:

const data: NavItem[] = [
  {
    key: "dashboard",
    target: "dashboard",
    text: "Dashboard",
    children: []
  },
  {
    key: "sales",
    target: "sales",
    text: "Sales"
  }
];

data is not an array of NavItem items, in order to get that you'll need to create instances of NavItem using the new keyword, for example:

const data: NavItem[] = [
  Object.assign(new NavItem(), {
    key: "dashboard",
    target: "dashboard",
    text: "Dashboard",
    children: []
  }),
  Object.assign(new NavItem(), {
    key: "sales",
    target: "sales",
    text: "Sales"
  })
];

The compiler doesn't complain about doing that because typescript is based on structural subtyping and the two types share the same structure.
But it's easy to see why it's wrong by adding methods to NavItem:

class NavItem {
  key: string;
  text: string;
  target: string;
  children?: NavItem[];

  getKey() {
    return this.key;
  }
}

Now try this with your code:

console.log(data[0].getKey());

And you'll end up with:

Uncaught TypeError: data[0].getKey is not a function

You should even get a compilation error saying that getKey is missing in your array items.

If you only intend to use NavItem as data objects then it doesn't really matter, but you should just use interfaces or type aliases instead as they don't get compiled to js and you avoid redundant memory usage:

interface NavItem {
  key: string;
  text: string;
  target: string;
  children?: NavItem[];
}

Edit

After a comment from @Zze I've decided to add a simple constructor that uses the "power" of Object.assign:

class NavItem {
  key: string;
  text: string;
  target: string;
  children?: NavItem[];

  constructor(data?: NavItem) {
      if (data) {
          Object.assign(this, data);
      }
  }
}

then:

new NavItem({
    key: "sales",
    target: "sales",
    text: "Sales"
})
Sign up to request clarification or add additional context in comments.

3 Comments

Is there a reason you didn't suggest the creation of a constructor() for NavItem ?
@Zze Was just trying to keep it to minimum change and to focus on the problem. Also, in this case it makes sense to use Object.assign in this manner instead of having the usual boilerplate constructor as it won't do anything other than assigning values to the members.
@Zze Check my revised answer, I added a constructor that used Object.assign

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.