2

I have a JavaScript class I would like to supply with default values using an object. I only want the default values to be part of the class if user input is not otherwise supplied for some of the values. However, I am not sure how to implement this. Here is my class:

// Class definition, properties, and methods
class iTunesClient {
  constructor(options) {
    this.term = options.terms;
    this.country = options.country;
    this.media = options.media;
    this.entity = options.entity;
    this.attribute = options.attribute;
    this.callback = options.callback;
    this.limit = options.limit;
    this.lang = options.lang;
    this.version = options.version;
    this.explicit = options.explicit;
    this.url = options.url;
  }
}

Here are my default values:

// Default values defined according to iTunes API
const defaults = {
  terms: 'default',
  country: 'US',
  media: 'all',
  entity: '',
  attribute: '',
  callback: '',
  limit: 50,
  lang: 'en-us',
  version: 2,
  explicit: 'yes',
  url: '',
};

I realize this is possible through default parameters for functions, but I would rather supply an object containing the default values.

Doug Wilhelm
  • 776
  • 9
  • 26
  • What is the issue with code at Question? – guest271314 Sep 29 '17 at 20:10
  • Usually, you would use `Object.assign()` to merge your default options with the passed in options into a new object and then use that to assign all your values with. – jfriend00 Sep 29 '17 at 20:10
  • Possible duplicate of [How can I merge properties of two JavaScript objects dynamically?](https://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically) – gautsch Sep 29 '17 at 20:19

4 Answers4

8

A typical way to do this is to use Object.assign() to merge passed-in values with default values:

// Class definition, properties, and methods
class iTunesClient {
  constructor(options) {

    // Default values defined according to iTunes API
    const defaults = {
      terms: 'default',
      country: 'US',
      media: 'all',
      entity: '',
      attribute: '',
      callback: '',
      limit: 50,
      lang: 'en-us',
      version: 2,
      explicit: 'yes',
      url: '',
    };      

    let opts = Object.assign({}, defaults, options);
    this.term = opts.terms;
    this.country = opts.country;
    this.media = opts.media;
    this.entity = opts.entity;
    this.attribute = opts.attribute;
    this.callback = opts.callback;
    this.limit = opts.limit;
    this.lang = opts.lang;
    this.version = opts.version;
    this.explicit = opts.explicit;
    this.url = opts.url;
  }
}

To explain how Object.assign() works here:

  1. It starts with {} as a target (an empty object)
  2. Then it copies all the defaults into the empty object
  3. Then it copies all the passed in properties into that same target
  4. Then it returns the target object which you use for initializing all your instance data

Of course, if your instance property names are the same as the ones in your options object, you could do this in a more DRY fashion like this:

// Class definition, properties, and methods
class iTunesClient {
  constructor(options) {

    // Default values defined according to iTunes API
    const defaults = {
      terms: 'default',
      country: 'US',
      media: 'all',
      entity: '',
      attribute: '',
      callback: '',
      limit: 50,
      lang: 'en-us',
      version: 2,
      explicit: 'yes',
      url: '',
    };      

    let opts = Object.assign({}, defaults, options);

    // assign options to instance data (using only property names contained
    //  in defaults object to avoid copying properties we don't want)
    Object.keys(defaults).forEach(prop => {
        this[prop] = opts[prop];
    });
  }
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

You could do it like this:

class iTunesClient {
  constructor(options) {

    // Default values defined according to iTunes API
    const defaults = {
      terms: 'default',
      country: 'US',
      media: 'all',
      entity: '',
      attribute: '',
      callback: '',
      limit: 50,
      lang: 'en-us',
      version: 2,
      explicit: 'yes',
      url: '',
    };      

    this.term = opts.terms || defaults.terms;
    this.country = opts.country || defaults.country;
    this.media = opts.media || defaults.media;
    this.entity = opts.entity || defaults.entity;
    this.attribute = opts.attribute || defaults.attribute;
    this.callback = opts.callback || defaults.callback;
    this.limit = opts.limit || defaults.limit;
    this.lang = opts.lang || defaults.lang;
    this.version = opts.version || defaults.version;
    this.explicit = opts.explicit || defaults.explicit;
    this.url = opts.url || defaults.url;
  }
}

But you have to be wary of 'falsy' values, e.g. if opts.limit is passed in as 0 then this.limit will be set to defaults.limit value, even though opt was defined.

D-Money
  • 2,375
  • 13
  • 27
0

I think the better solutions for that, stackoverflow.com/a/48775304/10325885

class User {
    constructor(options = {}) {
        this.name = options.name || "Joe";
        this.age = options.age || 47;
    }
}

I think is much cleaner to read, if you just use ||.

LessQuesar
  • 3,123
  • 1
  • 21
  • 29
-1

For something a little more modern than the examples above you can use destructuring and default parameters.

class iTunesClient {
  constructor({
    term = '',
    country = 'US',
    media = 'all',
    entity = '',
    attribute = '',
    callback = '',
    limit = 50,
    lang = 'en-us,
    version = 2,
    explicit = 'yes',
    url = '',
  }) {
    this.term = terms;
    this.country = country;
    this.media = media;
    this.entity = entity;
    this.attribute = attribute;
    this.callback = callback;
    this.limit = limit;
    this.lang = lang;
    this.version = version;
    this.explicit = explicit;
    this.url = url;
  }
}

That way any params that are not part of the constructor object will get set to the default. You can even pass an empty object and just get all defaults.

derikb
  • 59
  • 1
  • 7