Skip to content

Classes extending mixins lose inheritance when target is es5 #37601

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
juanmendes opened this issue Mar 25, 2020 · 1 comment
Closed

Classes extending mixins lose inheritance when target is es5 #37601

juanmendes opened this issue Mar 25, 2020 · 1 comment
Labels
Question An issue which isn't directly actionable in code

Comments

@juanmendes
Copy link

juanmendes commented Mar 25, 2020

If I create a class that depends on the behavior of a mixin, the code works fine when compiled into es6 but breaks when compiled into es5.

I had created a mixin that allows classes to automatically unsubscribe in ngOnDestroy() and it was working fine until I discovered that I needed to support IE 11, so I changed my target to es5 and noticed that the prototype chain was broken.

This problem does not happen when the class using the mixin doesn't need to know about the mixin, but in my case it did so the mixin function calls happened in the extends clause, using Object as its base, which isn't supported.

Script Version:
Nightly

Search Terms:
mixins es5

Code
I based this off https://mariusschulz.com/blog/mixin-classes-in-typescript but I added the code that makes it fail (in es5), that is, the User class wants to use the methods getTimestamp() and getCoolName() from two mixins. For that reason, I couldn't just use
const TimestampedUserWithCoolName = Timestamped(HasCoolName(User));

type Constructor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    getTimeStamp() {  return  Date.now();  }
  };
}

function HasCoolName<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    getCoolName() {  return "HotDiggityDog"; }
  };
}

class User extends Timestamped(HasCoolName(Object)){
  getUserName() { 
    return this.getCoolName() + this.getTimeStamp();
  }
}
const user = new User();
// Errors in es5 but not in es6
user.getUserName();
// Also errors, it lost the whole prototype chain
user.getTimeStamp();

Expected behavior:
User instances should have access to all three methods: getTimeStamp,getCoolName,getUserName

Actual behavior:
It works when compiled to es6 but throws a runtime error when compiled into es5

Workaround
Instead of using Object as the base of the mixin chain in an extends clause, just use class {}. See https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work

I realize this is very much related to the FAQ answer above, but it was not immediately obvious to me when I first read it. Thank God for the similar issues listed below when creating the questions. I decided to still create the issue in case someone else has the same problem since this workaround is a better one if people experience the problem I had where the base object didn't have to be Object or Error or Array, it was just to be able to call mixins in the extend clause so the class can use the mixin methods.

Playground Link:
https://www.typescriptlang.org/play/?target=1&ts=3.9.0-dev.20200324&ssl=20&ssc=3&pln=20&pc=14#code/C4TwDgpgBAwg9gOwM7AE4FcDGw6oDwAqUAvFAN4C+AfCVAhAO5QAUAdOwIaoDmSAXFA4IQAbQC6AShI0CAbgBQ8gGboE2AJaIoBdQFsIKDrsgATQgCEOSaBAAewCAhNJYiFBmy4qzS9YEFfCCkyeSgoVAhgdFQEKEwAGysXOwcnF0DyULCobkidfQBlYCMwZmCoLOzwyOjYqAARDgdWBDgGMoUqiiyKBW7lVQ0tAAkreDh4gDkjCAsrG3tHZ1dkNCwcVG9A-0DgrIiomLjEpGTFtKgMkKrc4HGpmbLyCqqwg9qoACJhuGB69W43HUoHqcG4n062W6YV68n6CSSUAAqtZUFAUksXPkDMVjBATMxRkh7tN9MwAPIAIwAVhBsBIJNccpEURBUKSIE8yC9su8jsAABbqJCsW4kx5SADUUEFwtFeT0ECKJQ6PTh8kwbmAUHQqNo9CYrNQqoA9CaoABRVCoXAudSxAwAViglPQ2ta2vt6KQADZ5Lq2fLgEaOabzQBBeJIODo622qDAqDxOAoGUC6AMAUTaBgG04UCQf2ooPY5XGDpAA

Related Issues:
#33023 seems related since changing the mixin chain to Timestamped(HasCoolName(class{})) worked. It convinced me that extending native objects is the root of my "problem".

@typescript-bot
Copy link
Collaborator

This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants