angular-redux/store

`@select` + AoT + Angular CLI === sadness

SethDavenport opened this issue ยท 29 comments

When you run in JIT mode, it works fine. When you run code using @select in AoT mode, it just turns into a no-op.

Looking at the output suggests that the decorator code is simply missing after transpilation.

@clbond did some experiments with angular-cli's AoT integration and found this: https://github.com/angular/angular-cli/blob/master/packages/webpack/src/loader.ts#L19

This code appears to be stripping all decorators out of the AST (!)

Apparently commenting out those lines in the cli's loader causes ng2-redux's @select to work just fine again.

Once #234 is fixed I'll put together a sample repo and file a bug with the CLI team asking for more context about this code.

Meanwhile, we're checking to see if it works with raw ngc.

With some hacks to workaround #234 and #235 we were able to get @select working with a raw ngc (non CLI) compile. So it looks like this is specific to the angular CLI's webpack loader.

When I tried to use @select(state => state.value) private property, I got
Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function

Is this issue investigating that kind of error?

Nope - that's an AoT limitation with anonymous functions. You'll need to do either:

// Baby-sit AoT's limitations with anonymous functions and needing things to be
// exported.
export function selectValue(s) { return s.value; }; 

// ...

@select(selectValue) value

or

// Looks for a property on state called 'value'
@select('value') property;

or even

// Infers state.value from the property name
@select() value;

In general, AoT has a lot of non-obvious limitations with what TypeScript constructs can be used at the boundary of NgModules. Documentation has also been lacking. The best resource I know of right now is some experiments being conducted by my work colleagues as they migrate some larger codebases to AoT.

https://github.com/rangle/angular-2-aot-sandbox

@SethDavenport would you mind sharing how you managed to get your custom decorator @select to work with an AOT build?

I'm trying to use ngrx/effects in my Ionic 2 app. Ionic 2 does not use the angular-cli, instead it uses ngc directly when it builds an app for production. Therefore, it shouldn't be prone to the issue in the angular-cli webpack loader, which strips out all custom decorators.

Instead, I think we're coming up against this section in the angular compiler codebase which appears to only allow a whitelist of decorators.

Did you find a way of getting around this?

Hi @fiznool - I can't speak for that piece of code you linked: I'm aware of it but I'm not clear of when during AoT it gets invoked.

That said, my colleagues have put together a set of test repos in an attempt to flush out AoT's "hidden gems" :)

Their tests seem to indicate that both ngrx's @Effect and our @select can be made to work with raw ngc.

Take a look, hopefully that will shed some light on it?

@SethDavenport thanks! Looks like a great resource. I'll try to add an explicit test for ngrx/effects soon, just to confirm that it will indeed work - but as you've indicated it certainly should do, therefore I need to try and figure out how to get this working with Ionic ๐Ÿ˜„

@select appears to work now with Angular CLI beta.24.

Hey @SethDavenport,

I am getting the same issue as @NeoLSN mentioned: ERROR in Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 15:11 in the original .ts file), resolving symbol when trying to build my project with AoT.

I am using angular-cli 1.0.0 and Angular 4.0.2 - are you aware of this already ?

Thanks !

Can you share the line of code that's triggering the error?

Hi @SethDavenport,

thanks for your response. This is the line, which is triggering the error @select(appStore => appStore.base.id) routeId$: Observable<number>;. I am triggering the build with ng build --aot and I am using the latest version of angular-redux/store (6.2.0).

(If I remove the line, the select decorators from the other components are triggering the same error).

@SethDavenport
Hi, I'm facing the same error with angular cli 1.0 and Angular 4.
If I disable the aot by doing --aot=false everything works fine.

This is a known limitation of the AoT compiler. It's not very good with inline arrow functions in certain cases.

Instead, try this:

export function selectBaseId(appstore) {
  return appStore.base.id;
}

class YourClass {
  // ...
  @select(selectBaseId) routeId$: Observable<number>;
}

Alternately, for a simple selector like this you can do:

export function selectBaseId(appstore) {
  return appStore.base.id;
}

class YourClass {
  // ...
  @select(['base', 'id']) routeId$: Observable<number>;
}

Apart from being more succinct, it will also not explode if base is undefined or null.

Thanks, @SethDavenport ! It's now working when using @select(['base', 'id']) routeId$: Observable<number>; !

The need to declare an entire secondary function just to use DI is is a pretty heavy work around ...

This is not related to DI. This is a limitation of angular's AoT compiler.

@SethDavenport is there anything on the angular-cli roadmap? The syntax is pretty gnarly.

I can't comment on the Angular team's plans unfortunately. However there are a few things you can already do with @select that might help. For example, if your selector function is simply of the form state => state.propName, you can pass a string to @select:

// shorthand for @select(state => state.propName), but AoT-safe
@select('propName') propName$;

Alternately, if your selector is a property path (e.g. state => state.foo.bar) you can specify a PathSelector:

// shorthand for @select(state => state.foo.bar), but AoT-safe
@select(['foo', 'bar']) fooBar$;

If you're doing anything much more complex than this, it's likely to be something that should be separated out and unit tested anyway.

I feel ya, but both of those use string properties which is not typesafe and wont play nicely with either VSCode or WebStorm tooling.

I use vscode all the time without issues. If you're wanting to enforce that those paths conform to the store, then use a function selector. Again, most people keep their function selectors in a separate file for a number of reasons:

  • AoT support
  • Unit testability
  • The ability to enhance them with things like reselect (memoization) etc.

There are lots of different programming styles, and lots of levels to which typing is enforced. That's why we support a few different syntaxes. Some people want everything to be typesafe to the nth degree. They tend to use exported function selectors or ngRedux.select().

There are people who are comfortable with property or path selectors because they are super declarative, and that matters more to them than enforcing types 100% of the time.

Would I prefer that Angular's AoT compiler get comfortable with lambdas? Yes I would. But it's not my call.

Well said @SethDavenport - i moved the ticket over to the Angular-CLI team. I'll probably end up using ngRedux.select() as a stop gap. There are other projects that suffer from this same compilation issue ( TypeORM )