Store: ngxs not compatible with target: es2015

Created on 16 Jan 2019  Â·  29Comments  Â·  Source: ngxs/store

change "target": "es5" --> "target": "es2015", in tsconfig.json

```
main.ts:21 TypeError: Class constructor AppState cannot be invoked without 'new'
at results.push.selectorsToApply.map.a (ngxs-store.js:1386)
at Array.map ()
at fn (ngxs-store.js:1386)
at Store.selectSnapshot (ngxs-store.js:1476)
at new AppHandler (app.handler.ts:30)
at core.js:19605
at _callFactory (core.js:26903)
at _createProviderInstance (core.js:26846)
at resolveNgModuleDep (core.js:26805)
at _callFactory (core.js:26914)

````

core critical

All 29 comments

@xmlking Thanks for reporting can you provide the info from the issue template?

@markwhitfeld we should probably add an integration test to make sure it works with es2015. It looks like the issue is sitting in the selector-util.ts.

line 51 has:

results.push(...selectorsToApply.map(a => getSelectorFn(a)(state)));

im going to assume something is going wrong with the spread

any workaround for this issue?

I cannot try differential loading 😢
https://speakerdeck.com/mgechev/2019?slide=5

It may be my previous environment (Angular CLI version , TypeScript etc) which caused the above error. But after upgraded to latest Angular CLI 8, I don't face this issue. we can close this issue.
Thanks

working sample....
https://github.com/xmlking/ng-differential-loading

Digging deeper, found the root cause at:
https://github.com/xmlking/ng-differential-loading/blob/master/projects/webapp/src/app/app.module.ts#L54
if we comment out this block in app.module.ts, it works.

    {
      provide: APP_INITIALIZER,
      useFactory: noop,
      deps: [RouteHandler],
      multi: true
    }

minimal repo to reproduce:
https://github.com/xmlking/ng-differential-loading

yarn install
ng start

@xmlking I checked in the latest version and this problem does not reproduce

@splincode how do i get latest version of @ngxs/store ? cont find latest package greater then 3.4.3
in npm?

@xmlking

npm i @ngxs/store@dev

i upgraded to

"@ngxs/devtools-plugin": "^3.4.3-dev.master-a1ac2a6",
"@ngxs/store": "^3.4.3-dev.master-a1ac2a6",

please see above https://github.com/ngxs/store/issues/773#issuecomment-486867640 to reproduce

still getting error
image

@xmlking

How I can reproduce?

Please clone above simplified project and run ‘yarn’ and ‘ng serve’
See error in the console

Having the same issue. NGXS breaks after upgrading to Angular 8. I am on 3.5.0.

@zakton5 can you help me, provide simple repro?

@splincode Use @xmlking 's project but set the suppressErrors flag to false in app.module.ts.

@xmlking
I was debugging a little bit. The AppHandler's constructor is invoked before NgxsModule makes internal initializations, thus it tries to selectSnapshot of state that doesn't exist at that moment.

This worked for me:

// Noop handler for factory function
export function noop(injector: Injector) {
  return () => {
    console.log(injector.get(AppHandler))
  };
}

providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: noop,
      deps: [Injector],
      multi: true,
    },
  ],

Digging deeper. The platform is bootstrapped w/o errors, but it causes indescribable behavior.

If we target es5 then the installed property equals undefined, it's still not selected from the store.

const installed = this.store.selectSnapshot(AppState.installed); // undefined

In that case es2015 tells you that something is really wrong, that's why it fails. It fails because targetting es2015 doesn't force compiler to emit function instead of class.

So AppState looks different in different targets:

class AppState {} // es2015
var AppState = function() { ... }; // es5

The selector tries to access static metadata, if it doesn't exist, the selector returns plain class:

function getSelectorFn(selector) {
    var metadata = getSelectorMetadata(selector) || getStoreMetadata(selector);
    return (metadata && metadata.selectFromAppState) || selector;
}

The selectFromAppState property is not defined at this stage, thus it returns selector which is AppState. Then it tries to invoke the result of getSelectorFn, it fails with:

class AppState {}
AppState(arguments); // es2015 -> fail

var AppState = function() { ... };
AppState(arguments); // es5 -> OK

@xmlking I'm confirming it's not an issue in this case as it's related to the DI cycle of Angular itself, see my comment above.

Given the following pseudo algorithm regarding our case:

  • useFactory: noop + deps: [AppHandler]
  • callFactory(appModuleRef, factory, deps)
  • if deps.length === 1

    • factory(resolveNgModuleDep(appModuleRef, deps[0]))

    • resolveNgModuleDep -> AppHandler

    • createProviderInstance -> AppHandler

    • createClass -> AppHandler

    • if deps.length === 0 then new AppHandler()

    • AppHandler constructor -> selectSnapshot(AppState.installed)

    • fail in es2015, but not in es5 because of invoking class

You have to retrieve the instance of AppHandler lazily, because APP_INITIALIZER deps are initialized before initNgModule, that initializes module providers.

@zakton5 IMO this seems meaningless to say that 3.5.0 doesn't work with Angular 8 w/o providing any reproduction...

@xmlking woudn't you mind closing this issue or there is something else I can do for you? :blush:

So is this not gonna be fixed?
Why not just detect whether or not it's a class or a function at runtime?
ES2015 support is important.

What exactly would you want us to fix? Have you read the discussion above? :slightly_smiling_face:

@arturovt I just fixed it by patching the generated fesm2015 and esm2015.

$ yarn add @ngxs/[email protected] 
$ yarn add -D patch-package

Add this file

patches/@ngxs+store+3.5.1.patch

diff --git a/node_modules/@ngxs/store/esm2015/src/utils/selector-utils.js b/node_modules/@ngxs/store/esm2015/src/utils/selector-utils.js
index 0926560..fc107bc 100644
--- a/node_modules/@ngxs/store/esm2015/src/utils/selector-utils.js
+++ b/node_modules/@ngxs/store/esm2015/src/utils/selector-utils.js
@@ -96,7 +96,7 @@ export function createSelector(selectors, originalFn, creationMetadata) {
          * @param {?} argFn
          * @return {?}
          */
-        argFn => argFn(state))));
+        argFn => 'prototype' in argFn ? new argFn(state) : argFn(state))));
         // if the lambda tries to access a something on the
         // state that doesn't exist, it will throw a TypeError.
         // since this is quite usual behaviour, we simply return undefined if so.
diff --git a/node_modules/@ngxs/store/fesm2015/ngxs-store.js b/node_modules/@ngxs/store/fesm2015/ngxs-store.js
index dbd53f6..eee81b1 100644
--- a/node_modules/@ngxs/store/fesm2015/ngxs-store.js
+++ b/node_modules/@ngxs/store/fesm2015/ngxs-store.js
@@ -2163,7 +2163,7 @@ function createSelector(selectors, originalFn, creationMetadata) {
          * @param {?} argFn
          * @return {?}
          */
-        argFn => argFn(state))));
+        argFn => 'prototype' in argFn ? new argFn(state) : argFn(state))));
         // if the lambda tries to access a something on the
         // state that doesn't exist, it will throw a TypeError.
         // since this is quite usual behaviour, we simply return undefined if so.

and add patch-package to your postinstall scripts

package.json

{
    // ...
    "scripts": {
        // ...
        "postinstall": "patch-package"
    }
}

Sorry, I do not know what is patch-package and how is related. If you've got any problems then why wouldn't you want to open an issue with provided reproduction?

Couldn't get it to work anyway.
NGXS is completely incompatible with es2015 builds (this has got nothing to do with Angular), since you don't differ between methods and classes when calling them, so differential loading is completely useless.
Basically you can only use NGXS with the Angular Webpack Plugin.
Guess I'll have to migrate to Akita instead.
You should probably mention this in the docs instead of just closing the issue.

Couldn't get it to work anyway.
NGXS is completely incompatible with es2015 builds (this has got nothing to do with Angular), since you don't differ between methods and classes when calling them, so differential loading is completely useless.
Basically you can only use NGXS with the Angular Webpack Plugin.
Guess I'll have to migrate to Akita instead.
You should probably mention this in the docs instead of just closing the issue.

If you've got any problems then why wouldn't you want to open an issue with provided reproduction?

@marcus-sa

I noticed that all your comments have nothing to do with what I ask. Who are you talking to then?

I think that this is a hard problem to solve and it feels like @arturovt and @marcus-sa are talking a bit cross purposes. @marcus-sa do you think you would be able provide a reproduction? Potentially even a PR to fix the issue?
PS. @marcus-sa You are coming across as quite demanding and entitled, I'm sure you don't mean to. We are all contributors that do this in our own time and do not get paid for this work.

@markwhitfeld I didn't mean to be rude, so I'm sorry about that.
The closest I can give to a reproduction would be if you created any Angular project and use the ES2015 target while selecting some state.
I used the Angular CLI before where the workaround was simply to set the target to ES5.
I've just migrated everything to Bazel, where differential loading is done using Rollup & Babel.
I'm not familiar with the NGXS code, but from what I've taken a look at it requires some serious refactoring, because you haven't taken into consideration that when invoking a function, it can be a class.
This is "illegal" when using classes and in any other transpiled code.
E.g Babel enforces the same behavior using _classCallCheck

Almost 90% of all browsers used today supports ES2015+

@marcus-sa

If you've got any problems then why wouldn't you want to open an issue with provided reproduction? :slightly_smiling_face:

I just ran a simple hello world app locally with target=es2015 and it works fine...

I find it unacceptable to make noise without confirmation. I remember you opened an issue where you mentioned that "storage plugin is not AOT compatible" without providing any reproduction, but also made some noise in other issues, like: "we need support for AOT etc.". But actually ALL NGXS users (who have ever used the storage plugin) never have had any issues with the storage plugin and AOT compilation, because otherwise they would have reported it earlier.

I spent a lot of time to debug the reproduction, provided by @xmlking just to help him understand what's really going wrong here.

But from my perspective, the same situation happens again. You came, made some noise, said that everything is bad and nothing works, but don't want to show this behavior. I immediately get the impression that you make these issues up.

And this is only my subjective opinion. If you could have provided some reproduction then everyone, who sees it, can debug and probably make a fix. But you don't want it without any reason.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

thomas-burko picture thomas-burko  Â·  25Comments

xmlking picture xmlking  Â·  29Comments

Koslun picture Koslun  Â·  32Comments

armanozak picture armanozak  Â·  23Comments

amcdnl picture amcdnl  Â·  19Comments