Typescript: cannot create property inside function - function

I'm using TypeScript in my project and i am not able to assign a property to anything. I'm attempting to grab some data from a service and assign it to a private object declared using the constructor. However, i keep getting the same error: TypeError: Cannot create property [some property here] on [some data here].
Here's my code:
module MyModule.Controllers {
"use strict";
export interface ICurrentUser {
DisplayName: string;
Features: string[];
}
export class DashboardController {
authenticationService = new Services.AuthenticationService(this.$http);
static $inject = ["$http"];
constructor(private $http: ng.IHttpService, private currentUser: ICurrentUser, private currentUserFeatures: string[])
{
this.getCurrentUser();
this.getUserGroupForCurrentUser();
return;
}
getUserGroupForCurrentUser = () => {
this.authenticationService.getUserGroupForCurrentUser().then((authenticationId) => {
this.currentUser.Features = authenticationFeatures;
});
}
getCurrentUser = () => {
this.authenticationService.getCurrentUser().then((user) => {
this.currentUser.DisplayName = userName;
});
}
}
}

Cannot create property 'DisplayName' on string 'John Doe' –
This is on the following code:
this.currentUser.DisplayName = userName;
Seems that at runtime this.currentUser is actually a string John Doe. This points to the fact that the type declarations (which are effectively hints to the compiler about external systems not in its control) do not match the actual external system at runtime.

Related

Parsing Complex JSON Objects with inheritance

I'm building a batch process that includes a number of steps of varying types.
export interface IStep {
id: number;
icon: string;
name: string;
selected: boolean;
}
export class InitStep implements IStep {
id: number;
icon: string;
name: string;
selected = false;
}
export class InputStep implements IStep {
id: number;
icon: string;
name: string;
selected = false;
primaryKey: string;
file: File;
}
export class QueryStep implements IStep {
constructor () {
this.filters = [];
this.output_fields = [];
this.table_fields = [];
const filter = new Filter;
this.filters.push(filter);
}
get input_ids(): number[] {
return this.filters.map(filter => filter.input_id);
}
id: number;
icon: string;
name: string;
selected = false;
table: string;
table_fields: string[];
filters: Filter[];
output_fields: string[];
}
export class OutputStep implements IStep {
constructor() {
this.fields = [];
}
id: number;
icon: string;
name: string;
selected = false;
fields: string[];
}
export class DeliveryStep implements IStep {
constructor() {
this.output_ids = [];
}
id: number;
icon: string;
name: string;
selected = false;
output_ids: number[];
format: BatchOutputType;
frequency: BatchFrequencyType;
email: string;
password: string;
}
I want to be able to have an array of any combination/number of these steps and be able to save them to and read from localstorage.
const key = 'notgunnawork';
localStorage.setItem(key, JSON.stringify(this.steps));
const s = JSON.parse(key) as IStep[];
I knew there was a snowball's chance in hell this was going to parse correctly, obviously the parser doesn't know which steps belong to what classes ultimately. I was just wondering if there was a simple way to get my array to come out looking the same way it went in. I'll eventually be posting this list to the server and would like my .Net Core code to also be able to parse this JSON without me having to make a custom parser.
EDIT
Added the full classes of what Im trying to serialize, for more detail. The error I'm getting whenever I try to serialize and then deserialize is: "Unexpected token o in JSON at position 1"
So, I'm going to answer what I think your issue is, and if I'm wrong then feel free to ignore me 🙂
Your problem is that you have a bunch of classes with methods but when you serialize instances of these to JSON and then deserialize them back, you end up with plain-old JavaScript objects and not instances of your classes. One way to handle this is to use a custom deserializer which knows about your classes and can "hydrate" or "revive" the plain-old JavaScript objects into genuine class instances. The JSON.parse() function allows you to specify a callback parameter called reviver which can be used to do just that.
First, we need to set up a system by which the reviver will know about your serializable classes. I'm going to use a class decorator which will add each class constructor to a registry the reviver can use. We will require that a serializable class constructor be assignable to a type we can call Serializable: it needs to have a no-argument constructor and the things it constructs need to have a className property:
// a Serializable class has a no-arg constructor and an instance property
// named className
type Serializable = new () => { readonly className: string }
// store a registry of Serializable classes
const registry: Record<string, Serializable> = {};
// a decorator that adds classes to the registry
function serializable<T extends Serializable>(constructor: T) {
registry[(new constructor()).className] = constructor;
return constructor;
}
Now, when you want to deserialize some JSON, you can check if the serialized thing has a className property that's a key in the registry. If so, you use the constructor for that classname in the registry, and copy properties into it via Object.assign():
// a custom JSON parser... if the parsed value has a className property
// and is in the registry, create a new instance of the class and copy
// the properties of the value into the new instance.
const reviver = (k: string, v: any) =>
((typeof v === "object") && ("className" in v) && (v.className in registry)) ?
Object.assign(new registry[v.className](), v) : v;
// use this to deserialize JSON instead of plain JSON.parse
function deserializeJSON(json: string) {
return JSON.parse(json, reviver);
}
Okay now that we have that, let's make some classes. (I'm using your original definitions here, before your edits.) Note that we are required to add a className property and we must have a no-arg constructor (this happens for free if you don't specify a constructor, since the default constructor is no-arg):
// mark each class as serializable, which requires a className and a no-arg constructor
#serializable
class StepType1 implements IStep {
id: number = 0;
name: string = "";
prop1: string = "";
readonly className = "StepType1"
}
#serializable // error, property className is missing
class OopsNoClassName {
}
#serializable // error, no no-arg constructor
class OopsConstructorRequiresArguments {
readonly className = "OopsConstructorRequiresArguments"
constructor(arg: any) {
}
}
#serializable
class StepType2 implements IStep {
id: number = 0;
name: string = "";
prop2: string = "";
prop3: string = "";
prop4: string = "";
readonly className = "StepType2"
}
#serializable
class StepType3 implements IStep {
id: number = 0;
name: string = "";
prop5: string = "";
prop6: string = "";
readonly className = "StepType3"
}
Now let's test it out. Make some objects as you would normally do, and put them in an array:
// create some objects of our classes
const stepType1 = new StepType1();
stepType1.id = 1;
stepType1.name = "Alice";
stepType1.prop1 = "apples";
const stepType2 = new StepType2();
stepType2.id = 2;
stepType2.name = "Bob";
stepType2.prop2 = "bananas";
stepType2.prop3 = "blueberries";
stepType2.prop4 = "boysenberries";
const stepType3 = new StepType3();
stepType3.id = 3;
stepType3.name = "Carol";
stepType3.prop5 = "cherries";
stepType3.prop6 = "cantaloupes";
// make an array of IStep[]
const arr = [stepType1, stepType2, stepType3];
And let's have a function which will examine the elements of an array and check to see if they are instances of your classes:
// verify that an array of IStep[] contains class instances
function verifyArray(arr: IStep[]) {
console.log("Array contents:\n" + arr.map(a => {
const constructorName = (a instanceof StepType1) ? "StepType1" :
(a instanceof StepType2) ? "StepType2" :
(a instanceof StepType3) ? "StepType3" : "???"
return ("id=" + a.id + ", name=" + a.name + ", instanceof " + constructorName)
}).join("\n") + "\n");
}
Let's make sure it works on arr:
// before serialization, everything is fine
verifyArray(arr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
Then we serialize it:
// serialize to JSON
const json = JSON.stringify(arr);
To demonstrate your original problem, let's see what happens if we just use JSON.parse() without a reviver:
// try to deserialize with just JSON.parse
const badParsedArr = JSON.parse(json) as IStep[];
// uh oh, none of the deserialized objects are actually class instances
verifyArray(badParsedArr);
// Array contents:
// id=1, name=Alice, instanceof ???
// id=2, name=Bob, instanceof ???
// id=3, name=Carol, instanceof ???
As you can see, the objects in badParsedArr do have the id and name properties (and the other class-specific instance properties like prop3 if you checked) but they are not instances of your classes.
Now we can see if the problem is fixed by using our custom deserializer:
// do the deserialization with our custom deserializer
const goodParsedArr = deserializeJSON(json) as IStep[];
// now everything is fine again
verifyArray(goodParsedArr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
Yes, it works!
The above method is fine, but there are caveats. The main one: it will work if your serializable classes contain properties which are themselves serializable, as long as your object graph is a tree, where each object appears exactly once. But if you have an object graph with any kind of cycle in it (meaning that the same object appears multiple times if you traverse the graph multiple ways) then you will get unexpected results. For example:
const badArr = [stepType1, stepType1];
console.log(badArr[0] === badArr[1]); // true, same object twice
const badArrParsed = deserializeJSON(JSON.stringify(badArr));
console.log(badArrParsed[0] === baddArrParsed[1]); // false, two different objects
In the above case, the same object appears multiple times. When you serialize and deserialize the array, your new array contains two different objects with the same property values. If you need to make sure that you only deserialize any particular object exactly once, then you need a more complicated deserialize() function which keeps track of some unique property (like id) and returns existing objects instead of creating new ones.
Other caveats: this assumes your serializable classes have instance properties consisting only of other serializable classes as well as JSON-friendly values like strings, numbers, arrays, plain objects, and null. If you use other things, like Dates, you will have to deal with the fact that those serialize into strings.
Exactly how complicated serialization/deserialization is for you depends heavily on your use case.
Okay, hope that helps. Good luck!

HttpClient how to convert nested json and map to model domain

Hi I am pretty new to programing and i have been looking for an answer on the web for days for my problem without finding anything that works for me no matter what i do. I am receiving nest json as response. The json object amongst other things seem to have a dynamic key. What i want is to transform the json object to my model so that i can easily access the data in my template. Appreciate any help
here is an example of the json data
Amadeus API response
Here is my service
getResults(params: any) {
this.getItiniraries(params).subscribe((res) => {
this.itinirary = res;
// console.log(res);
this.updatedResults.next(this.itinirary);
});
}
getItiniraries(params: any): Observable<Itinirary> {
return this.http.get<Itinerary>('http://localhost:4202/api/itinirary' , {params: params})
; }
the models
Itinirary model
import { Result } from './result.model';
import { Meta } from '#angular/platform-browser';
// Model for data from Amadeus flight affiliate search
export class Itinirary {
public meta: Meta;
public results: Result[];
constructor(res: any) {
this.meta = res.meta;
this.results = res.results;
}
}
Result Model
import { Flight } from './flight.model';
import { Fare } from './fare.model';
export class Result {
public outbound_duration: string;
public outbound_flights: Flight[];
public inbound_duration: string;
public inbound_flights: Flight[];
public fare: Fare;
public cabin_code: string;
public fare_family: string;
public travel_class: string;
public merchant: string;
public airline: string;
public deep_link: string;
constructor(result: any) {
this.outbound_duration = result.outbound.duration;
this.outbound_flights = result.outbound.flights;
this.inbound_duration = result.inbound.duration;
this.inbound_flights = result.inbound.duration;
this.fare = result.fare;
this.cabin_code = result.cabin_code;
this.fare_family = result.fare_family;
this.travel_class = result.travel_class;
this.merchant = result.merchant;
this.airline = result.airline;
this.deep_link = result.deep_link;
}
}
Flight model
import { BookingInfo } from './bookingInfo.model';
export class Flight {
public departs_at: Date;
public arrives_at: Date;
public marketing_airline: string;
public operating_airline: string;
public flight_number: number;
public aircraft: number;
public booking_info: BookingInfo;
public origin_airport: string;
public origin_terminal: string;
public destination_airport: string;
public destination_terminal: string;
constructor(flight: any) {
this.departs_at = flight.departs_at;
this.arrives_at = flight.arrives_at;
this.marketing_airline = flight.marketing_airline;
this.operating_airline = flight.operating_airline;
this.flight_number = flight.flight_number;
this.aircraft = flight.aircraft;
this.booking_info = flight.booking_info;
this.origin_airport = flight.origin_airport;
this.origin_terminal = flight.origin_terminal;
this.destination_airport = flight.destination_airport;
this.destination_terminal = flight.destination_terminal;
}
}
Meta model
import { Carrier } from './carrier.model';
export class Meta {
public carriers: {[key: string]: Carrier };
constructor(meta: any) {
this.carriers = meta.carriers;
}
}
Carrier Model
export class Carrier {
public identifier: string;
public name: string;
public logoSmall: string;
public logoMedium: string;
constructor(carrier: any) {
this.identifier = carrier;
this.name = carrier.name;
this.logoSmall = carrier.logos.samll;
this.logoMedium = carrier.logos.medium;
}
}
in my flight model i would also like to add two properties date and time that derives from departure_at and arrival_at.
Basically i want to be able to pass the whole Intinirary object to my view so that i can through string iterpolation get the values.
ex after using ngFor or let result of itinirary.results
{{ result.outbound_flights[0].departure_date }} etc.
would really appreciate good guidance
updated service
getItiniraries(params: any): Observable<any> {
return this.http.get<any>('http://localhost:4202/api/itinirary' ,
{params: params})
.pipe(map((AmdResponse) => {
const parsedRes = JSON.parse(AmdResponse);
const itin = new Itinirary(parsedRes);
return itin;
} )); }
Updated meta Model and carrier Model
Meta
import { Carrier } from './carrier.model';
export class Meta {
public carriers: {[key: string]: Carrier };
constructor(meta) {
this.carriers = {};
Object.keys(meta.carriers).forEach(code => {
this.carriers[code] = new Carrier(meta.carriers[code]);
});
}
}
carrier model
export class Carrier {
public name: string;
public logoSmall: string;
public logoMedium: string;
constructor(cObject ) {
Object.keys(cObject).forEach(code => {
this.name = cObject.name;
});
Object.keys(cObject.logo).forEach(code => {
this.logoSmall = cObject.logos.samll;
this.logoMedium = cObject.logos.medium;
});
}
}
I also updated my Result model like this. Does it make sense?
import { Flight } from './flight.model';
import { Fare } from './fare.model';
export class Result {
public outbound_duration: string;
public outbound_flights: Flight[];
public inbound_duration: string;
public inbound_flights: Flight[];
public fare: Fare;
public cabin_code: string;
public fare_family: string;
public travel_class: string;
public merchant: string;
public airline: string;
public deep_link: string;
constructor(result) {
this.outbound_duration = result.outbound.duration;
// this.outbound_flights = this.loop(this.outbound_flights,
result.outbound.flights);
this.inbound_duration = result.inbound.duration;
// this.inbound_flights = this.loop(this.inbound_flights,
result.inbound.flights);
this.fare = new Fare(result.fare);
this.cabin_code = result.cabin_code;
this.fare_family = result.fare_family;
this.travel_class = result.travel_class;
this.merchant = result.merchant;
this.airline = result.airline;
this.deep_link = result.deep_link;
for (let i = 0; i < result.outbound.flights.length; i++) {
this.outbound_flights[i] = new Flight(result.outbound.flights[i]);
}
for (let i = 0; i < result.inbound.flights.length; i++) {
this.inbound_flights[i] = new Flight(result.inbound.flights[i]);
}
}
// loop(a, b) {
// for (let i = 0; i < b.length; i++) {
// a[i] = new Flight(b[i]);
// }
// return a;
// }
}
I tested both with a function or seperate loop.
I also added a dateFormatterService in my flight model, was not sure though where to import it to as i could not import it to constructor.
import { BookingInfo } from './bookingInfo.model';
import { DateFormatterService } from '../../Shared/dateFormatter.service';
export class Flight {
private df: DateFormatterService; // can i have it here instead of constructor?
public departs_date: string;
public departs_time: string;
public arrives_date: string;
public arrives_time: string;
public marketing_airline: string;
public operating_airline: string;
public flight_number: number;
public aircraft: number;
public booking_info: BookingInfo;
public origin_airport: string;
public origin_terminal: string;
public destination_airport: string;
public destination_terminal: string;
constructor(flight: any ) {
const depart_at = new Date(flight.departs_at);
const arrive_at = new Date(flight.arrives_at);
this.departs_date = this.df.transformDate(depart_at);
this.departs_time = this.df.transformTime(depart_at);
this.arrives_date = this.df.transformDate(arrive_at);
this.arrives_time = this.df.transformTime(arrive_at);
this.marketing_airline = flight.marketing_airline;
this.operating_airline = flight.operating_airline;
this.flight_number = flight.flight_number;
this.aircraft = flight.aircraft;
this.booking_info = new BookingInfo(flight.booking_info);
this.origin_airport = flight.origin_airport;
this.origin_terminal = flight.origin_terminal;
this.destination_airport = flight.destination_airport;
this.destination_terminal = flight.destination_terminal;
}}
As a preamble, understand there is no way to receive a JSON string (from an API or elsewhere) and make it become an instance of a specific custom Class.
I think you started with the right idea in your Meta constructor.... that you need to parse the received data into your own objects. You just need to go further, by explicitly parsing all properties of the received JSON into new objects (of your classes) one at a time until you're done. Tedious, maybe, but required if you want your own class hierarchy to represent the received data (which may NOT be needed, but that's not for me to decide).
I assume you've already got a JS literal object from the data received from the API response, something like this:
const itins = JSON.parse( amadeusResponse );
Then, you let your classes do the work:
const meta = new Meta( itins.meta );
//or
const alternate = new Meta();
alternate.fromJSON( itins.meta );
By the above, you can see you can either have a method that reads JSON data, or a constructor. The choice would be made by whether you would expect to ever create a Meta object without first having the JSON (in which case the method alternate is likely better).
In either case, the implementation reads the JS object you give it, and parses the received data structure into the structure you want to have in your local class instances. For example, in the Meta constructor...
constructor( meta ) {
this.carriers = {};
// which carriers did we get?
Object.keys(meta.carriers).forEach( code =>
this.carriers[code] = new Carrier( code, meta.carriers[code] )
);
In turn, the Carrier class constructor will read the "logos" and "name" properties, either into its fields, which may include even more class instances.
Keep going, until you're done.
If you want to create a tree of Javascript instances of models from JSON, you can map the JSON to the root class constructor like this:
getItiniraries(params: any): Observable<Itinerary> {
return this.http.get<Itinerary>('http://localhost:4202/api/itinirary', {params: params}).pipe(map(x => new Itinerary(x));
}
Then define the following constructor for all of your model classes:
constructor(obj) {
Object.assign(this, obj);
}
Then for all the children of a model class, replace these declarations public meta: Meta; with:
private _meta: Meta;
get meta() {
return this._meta;
}
set meta(value: any) {
this._meta = new Meta(value);
}
Same goes for children as Array:
private _results: Result[];
get results() {
return this._results;
}
set results(value: any) {
this._results = value.map(x => new Result(x));
}
Then your tree of objects will be composed of instances of your model classes, and you'll be able to benefit from the potential functions you'll define (for example data formatting functions, or whatever else)
using new es6 features it is quite easy this._results = value.map(x => new Result(x)); can bethis._results = [...value] this case if value is nested json array it will be flat as you want.

Angular: How to map JSON result from HttpClient request to class instances?

I have the following code which seems wrong:
public search(searchString: string): Observable<Array<ClientSearchResult>> {
let params = new HttpParams().set('searchString', searchString);
return this.http
.get<Array<ClientSearchResult>>(this.searchUrl, { params: params })
.map((results: ClientSearchResult[]) => results.map((r: ClientSearchResult) => new ClientSearchResult(r)));
}
I know that the API is returning a JSON object which is not the same as an instance of my TypeScript class. However, I want to use properties defined in the TypeScript class.
Is there a better way to map the array coming from my API call to an array that actually consists of instances of ClientSearchResult?
Here is the ClientSearchResult object:
import { Name } from './name';
export class ClientSearchResult {
public id: string;
public name: Name;
public dateOfBirth: Date;
public socialSecurityNumber: string;
public get summary(): string {
let result: string = `${this.name}`;
if (this.dateOfBirth)
result += ` | ${this.dateOfBirth.toLocaleDateString()}`;
return result;
}
constructor(source: ClientSearchResult) {
this.id = source.id;
this.name = new Name(source.name);
this.dateOfBirth = source.dateOfBirth? new Date(source.dateOfBirth) : undefined;
this.socialSecurityNumber = source.socialSecurityNumber;
}
public toString(): string {
return this.summary;
}
}
We use a wonderful library to map json to typescript objects.
https://github.com/shakilsiraj/json-object-mapper
json-object-mapper depends on reflect-metadata library as it is using decorators to serialize and deserialize the data.
As an option you may try TypeScript as operator to cast your API response to the ClientSearchResult type.
import { Http, Response } from '#angular/http';
public search(searchString: string): Observable<ClientSearchResult[]> {
const params = new HttpParams().set('searchString', searchString);
return this.http.get(this.searchUrl, { params: params })
.map((results: Response) => results.json() as ClientSearchResult[]);
}
This approach requires your model class to be used as an interface, or just to be an interface:
interface ClientSearchResult {
id: number;
// etc
}
I have been using this very nice (and up-to-date at the time of posting) library:
https://www.npmjs.com/package/class-transformer
It can handle very complex cases with nested classes and more.

Json property alias in typescript

Let's say I want to get a data from Visual Studio TFS and the response (as json) is in this kind of format:
{
"Microsoft.VSTS.Scheduling.StoryPoints": 3.0,
// ......
}
There's dot in the property name. Reading from other questions I found out that I can read that json in typescript by using an interface like this
export interface IStory { // I don't think this kind of interface do me any help
"Microsoft.VSTS.Scheduling.StoryPoints": number
}
And then I can use the property with this syntax:
var story = GetStoryFromTFS();
console.log(story["Microsoft.VSTS.Scheduling.StoryPoints"]);
But I'd prefer not to call the property like this, since the intellisense won't able to help me finding which property I want to use (because I call the property using a string).
In C# there is a JsonProperty attribute which enable me to create a model like this:
public class Story
{
[JsonProperty(PropertyName = "Microsoft.VSTS.Scheduling.StoryPoints")]
public double StoryPoints { get; set; }
}
And then I can use the property this way:
var story = GetStoryFromTFS();
Console.WriteLine(story.StoryPoints);
This way the intellisense will able to help me finding which property I want to use.
Is there something like JsonProperty attribute in typescript? Or is there any other, better way, to achieve this in typescript?
You have many options. Just keep in mind that all of these options require you to pass the original data to the class that will access it.
Map the values.
class StoryMap {
constructor(data: IStory) {
this.StoryPoints = data["Microsoft.VSTS.Scheduling.StoryPoints"];
}
StoryPoints: number;
}
Wrap the data.
class StoryWrap {
constructor(private data: IStory) {}
get StoryPoints(): number { return this.data["Microsoft.VSTS.Scheduling.StoryPoints"] };
}
Build a decorator to map the data.
function JsonProperty(name: string) {
return function DoJsonProperty(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.get = function () {
return this.data[name];
}
descriptor.set = function (value) {
this.data[name] = value;
}
}
}
class StoryDecorator
{
constructor(private data: IStory) {}
#JsonProperty("Microsoft.VSTS.Scheduling.StoryPoints")
get StoryPoints(): number { return 0 };
}

How do I pass constructor parameters to imported class in ECMAScript 6?

If I'm importing a class into some other script, how can I pass parameters to that class constructor in ES6 syntax?
I would like to do something like this. I've seen various suggestions like wrapping functions or using factory pattern, but is there a cleaner simpler way of doing this?
// This is sudo code
import SomeClass from './SomeClassPath';
var thing = SomeClass(params);
I see that there is some confusion in your question, so let me clarify.
In ES6, you may know that you have two strategies when you need to export a module. You can use a default export or multiple exports. Let's take a very basic example (a simple logger around console):
function info(msg) {
console.info(`[Info] ${msg}`);
}
function error(msg) {
console.error(`[Error] ${msg)`);
}
Default export
Here we must group our functions. The most idiomatic way of doing this in JavaScript is with an object literal (see Revealing Module Pattern):
export default {
info(msg) {
console.info(`[Info] ${msg}`);
},
error(msg) {
console.error(`[Error] ${msg}`);
}
};
Then, in our client code, we will use this module like so:
import logger from './logger'
logger.info('Hello!');
logger.error('Oops!');
Multiple exports
Here we can export our functions independently:
export function info(msg) {
console.info(`[Info] ${msg}`);
}
export function error(msg) {
console.error(`[Error] ${msg}`);
}
Then, in our client code, we will use this module like so:
import {info, error} from './logger'
info('Hello!');
error('Oops!');
Done.
I suggest you to understand how the ES6 module system works with our functional example. It is exactly the same thing with classes...
Singleton?
By reading the comments, I have seen another confusion that requires clarification: the Singleton.
Singleton is a design pattern that makes it possible to instantiate a class just once. Now imagine that our class is the following:
export default class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
};
We could use it like this:
import Person from './Person';
let me = new Person('Baptiste', 'Vannesson'),
you = new Person('David', 'Choi');
console.log(Object.is(me, you)); // false, so there are two instances of Person
console.log(me.firstName, me.lastName); // Baptiste Vannesson
console.log(you.firstName, you.lastName); // David Choi
As you can see, Person has nothing to do with Singleton! It would be a Singleton with the following Java-inspired implementation:
export default (() => {
class Person {
// Private constructor
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
return {
// Public static factory method
getInstance(firstName, lastName) {
if (!Person.instance) {
Person.instance = new Person(firstName, lastName);
}
return Person.instance;
}
};
})();
Client code:
import Person from './Person';
let me = Person.getInstance('Baptiste', 'Vannesson'),
you = Person.getInstance('David', 'Choi');
console.log(Object.is(me, you)); // true, so there is only one instance
console.log(me.firstName, me.lastName); // Baptiste Vannesson
console.log(you.firstName, you.lastName); // Baptiste Vannesson (still me!)
For the sake of simplicity, you may prefer to export an instance directly:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
};
export default new Person('David', 'Choi');
Client code:
import person from './person';
// Person is not a constructor but a simple instance
let me = person,
you = person;
console.log(Object.is(me, you)); // true
console.log(me.firstName, me.lastName); // David Choi
console.log(you.firstName, you.lastName); // David Choi
If you do that, this would be even simpler to use an object literal:
export default {
firstName: 'David',
lastName: 'Choi'
};
The client code does not change here.
Passing parameter to your es6 module is very simple and straight forward.
Just do this simple thing.
// This is sudo code
require('./SomeClassPath.js')(param);
Then inside the module file SomeClassPath.js do this
module.exports = function(param) {
....
}