16

This computed property lastSeenString used to be working for me:

export class User {
    public lastSeen?: Date;
    private _lastSeenString?:string = "";
    get lastSeenString():string {
        return Time.timeSince(this.lastSeen);
    }
}

But now lastSeenString returns "". The return statement in the getter is never called. How come? lastSeen is populated.

client:

it('Displays the user\'s "last seen"', () => {
    component.user.lastSeen = new Date();
    fixture.detectChanges();
    expect(component.user.lastSeenString).toBe("less than a minute ago");
    expect(page.lastSeen.innerText).toBe("last seen less than a minute ago");
});

My typescript compiles to es5. Why does code inside the getter not execute?

Time class which is supposed to get executed but doesn't:

export class Time {
    public static timeSince(date: Date) {
        let start = +new Date();
        let elapsed = +new Date() - start;
        var seconds = Math.floor((elapsed) / 1000);

        // var seconds = Math.floor((new Date() - +date) / 1000);

        //var seconds = Math.floor(((new Date().getTime()/1000) - +date))


        var interval = Math.floor(seconds / 31536000);

        if (interval > 1) {
            return interval + " years";
        }
        interval = Math.floor(seconds / 2592000);
        if (interval > 1) {
            return interval + " months";
        }
        interval = Math.floor(seconds / 86400);
        if (interval > 1) {
            return interval + " days";
        }
        interval = Math.floor(seconds / 3600);
        if (interval > 1) {
            return interval + " hours";
        }
        interval = Math.floor(seconds / 60);
        if (interval > 1) {
            return interval + " minutes";
        }
        return "less than a minute ago";
    }
}

actual implementation of the User class:

export var SERENA: User = {
   id: 1,
   lastSeenString: "",
    lastSeen: new Date("October 13, 2016 11:13:00"),
    badges: ["active User", "helper"],
    memberSince: new Date("October 13, 2014")
}

I see the implementation has lastSeenString = "", however I thought that would trigger the getter rather than just return "".

Update: I'm pretty sure it must be to do with just exporting that JSON object and calling it a User. I think I must have to new it up.

BeniaminoBaggins
  • 11,202
  • 41
  • 152
  • 287

2 Answers2

29

The getter is defined on the prototype of User - i.e. as part of the class itself. You need to build your instance using that class (e.g. with new User()) for the getter to work.

Currently you just build a vanilla JavaScript object, and then tell TypeScript that it's the same shape as a real User object. That passes type checking, because it is the same shape - reading any field on it will return values of the same type that you'd be expecting from a real User object - but that doesn't tell you anything about the underlying behaviour of those fields.

Tim Perry
  • 11,766
  • 1
  • 57
  • 85
  • 2
    Not only the getters and setters are gone in this case but also the public methods if any. Also look at the [following answer](http://stackoverflow.com/a/29759472/1886308) dealing with a congruent problem. Furthermore it is a good practice not to include methods (and getters and setters) in your domain entities to avoid this problem. – Gie Spaepen Dec 08 '16 at 09:31
  • @GieSpaepen While I see your point, if you only have properties (no methods) in the domain model, then that would be considered a "data class" which is a code smell and is recommended only for use in prototyping as it leads to tight coupling in larger projects. – BeniaminoBaggins Dec 08 '16 at 18:31
  • @GieSpaepen However the answer you posted is a good solution which I think I will use for my mock data :) – BeniaminoBaggins Dec 08 '16 at 19:03
  • this makes sense, but it also exposes a huge pain point with typecsript. how frustrating. classes just feel like syntactical sugar, what's the point of any of this – Wassim Katbey Mar 23 '22 at 21:02
4

In my case, I was getting object data from the network as JSON. Then assigned the data like this:

   let newUser = Object.assign(new User(), data)

I could access all of the public data and functions, but couldn’t access functions, getters, setters for objects inside this object. So I had no access to private data even though I could print them out.

   class User {
      name: string
      state: UserState
    }

   class UserState {
      private _stateInfo: string
      getter()......
    }

So I can access name, and can print out the data of state. But can’t access functions inside state.

The way to solve this is to initialize new UserState object when passing the data for User object.

demiculus
  • 1,243
  • 1
  • 12
  • 32
  • It is not clear how you solved your issue. Isn't the solution the opposite of your initial approach? `let newUser = new User(data)`, then inside the constructor, you put your `Object.assign(this, data)` – Léon Pelletier Jul 26 '18 at 16:12