store icon indicating copy to clipboard operation
store copied to clipboard

🐞[BUG]: 'Error: You have forgotten to import the NGXS module' after upgrading to 3.7.4

Open willocho opened this issue 2 years ago • 31 comments

Affected Package

The issue is caused by package @ngxs/3.7.4

Is this a regression?

Yes, the previous version in which this bug was not present was: 3.7.3

Description

After upgrading to 3.7.4, released earlier today on npm, our angular tests started failing.

🔬 Minimal Reproduction

The test would fail for a class that simply used the Select decorator, or depended on a class that used the Select decorator.

Minimally reproducible example:


import { TestBed } from "@angular/core/testing";

import { ScreenLayoutService } from "./screen-layout.service";
import { NgxsModule } from "@ngxs/store";
import { StateLocal } from "@shared/models/state/client/client-state.local";
import { StateSession } from "@shared/models/state/client/client-state.session";
import { StateRoot } from "@shared/models/state/state.main";
import { StateMirror } from "@shared/models/state/mirror/state.mirror";

describe("ScreenLayoutService", () => {
    beforeEach(() => TestBed.configureTestingModule({
        imports: [
            NgxsModule.forRoot([StateRoot, StateMirror, StateLocal, StateSession]),
        ],
        providers: [ ScreenLayoutService ]
    }));

    it("should be created", () => {
        const service: ScreenLayoutService = TestBed.get(ScreenLayoutService);
        expect(service).toBeTruthy();
    });
});

Where ScreenLayoutService is a an Angular service that uses @Select

https://stackblitz.com/...

🔥 Exception or Error




Chrome Headless 100.0.4896.88 (Linux x86_64) ERROR
  An error was thrown in afterAll
  error properties: Object({ longStack: 'Error: You have forgotten to import the NGXS module!
      at throwSelectFactoryNotConnectedError (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@ngxs/store/__ivy_ngcc__/fesm2015/ngxs-store.js:73:1)
      at createSelectObservableIvy (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@ngxs/store/__ivy_ngcc__/fesm2015/ngxs-store.js:3844:1)
      at createSelectObservable (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@ngxs/store/__ivy_ngcc__/fesm2015/ngxs-store.js:3800:1)
      at MergeMapSubscriber.project (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@ngxs/store/__ivy_ngcc__/fesm2015/ngxs-store.js:3931:1)
      at MergeMapSubscriber._tryNext (http://localhost:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/operators/mergeMap.js:44:1)
      at MergeMapSubscriber._next (http://localhost:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm2015/internal/operators/mergeMap.js:34:1)
      at MergeMapSubscriber ...


Environment


Libs:
- @angular/core version: 12.6.16
- @ngxs/store version: 3.7.4 (when breaking)


Browser:
- Chrome Headless 100.0.4896.88 (Linux x86_64)
-  
For Tooling issues:
- Node version: 12.22.7
- Platform:  Linux, Debian

Others:

Downgrading from 3.7.4 to 3.7.3 resolved the issue

willocho avatar Jun 09 '22 17:06 willocho

Same problem here but also while serving angular.

Frankitch avatar Jun 13 '22 10:06 Frankitch

The same happened to me with 3.7.4 with Angular 14.0.2

XardasLord avatar Jun 15 '22 14:06 XardasLord

I got an close issue, but instead of forgotten to import NGXS module, I'm getting Injector has already been destroyed Error: NG0205: Injector has already been destroyed. error properties: Object({ code: 205 }) Error: NG0205: Injector has already been destroyed. at R3Injector.assertNotDestroyed (node_modules/@angular/core/fesm2015/core.mjs:11360:1) at R3Injector.get (node_modules/@angular/core/fesm2015/core.mjs:11288:1) at localInject (node_modules/@ngxs/store/fesm2015/ngxs-store-internals.js:264:1) at node_modules/@ngxs/store/fesm2015/ngxs-store.js:3927:54

GabrielAPineiro avatar Jun 21 '22 09:06 GabrielAPineiro

@willocho are you able to install the 3.7.4-dev.master-310a613 version and give it a try? Just update all packages as follows:

"@ngxs/store": "3.7.4-dev.master-310a613",
"@ngxs/storage-plugin": "3.7.4-dev.master-310a613",
...

arturovt avatar Jun 21 '22 14:06 arturovt

@arturovt problem persists with the 3.7.4-dev.master-310a613 versions.

danielrie avatar Jun 22 '22 09:06 danielrie

I'm seeing the same You have forgotten to import the NGXS module' issue when running the application after upgrade. Oddly this works the first time when going to route that is subscribing to a slice of state via @Select decorator, subsequent visits to same route fail.

I have tried the updated 3.7.4-dev.master-310a613 package and makes no difference.

Removing the the @Select decorator and using the store directly fixes the issue, eg:

  //@Select(UserState) userState$: Observable<UserStateModel>;
  userState$ = this.store.select<UserStateModel>(UserState);

For further context this is in service scoped to a component. Eg (much simplified for brevity):

@Injectable() // NOTE: scope this to the page.
export class KanbanDataService implements OnDestroy {
  @Select(UserState) userState$: Observable<UserStateModel>;
   constructor(private store: Store){
      const sub = this.userSate$.subscribe(); // much simplified for brevity
      this.subscriptions.push(sub);
   }
  ngOnDestroy() {
    this.subscriptions.forEach(s => s.unsubscribe());
  }
}

jaibeales avatar Jun 22 '22 14:06 jaibeales

Exactly the same scenario happens to me as for @jaibeales

XardasLord avatar Jun 22 '22 20:06 XardasLord

I don't know if it can help, but I encountered the same error in 3.7.3 and Angular 13.

The reason it failed is subscribing the selector resulting observable in the component constructor, instead of ngOnInit, in fact, not respecting the Angular component lifecycle.

@Select(BananaState.bananas) bananas$: Observable<Banana>;

// don't
constructor(){
  this.bananas$.subscribe(banana => doSometing());
}

//do
ngOnInit(){
  this.bananas$.subscribe(banana => doSometing());
}

lehmstedt avatar Jun 23 '22 16:06 lehmstedt

After a lengthy discussion, we decided to deprecate the Select decorator since it has impossible bugs to fix (just because Angular itself doesn't allow it). We're also planning to drop it in the future. It's complicated to maintain since we have to invest a lot of time into a single Select decorator rather than focusing on other essential issues. It's not type-safe. It has problems in server-side rendering and module federation. We tried to fix the server-side rendering issue initially, leading to a dead end.

For now, start replacing the Select decorator with store.select. You also shouldn't have any issues with 3.7.4 if you drop Select decorator usages.

arturovt avatar Jun 27 '22 21:06 arturovt

First of all great thanks to @lehmstedt for describing what was the cause for me getting the error in about 5 - 6 of of 52 components using it in my app. I had just upgraded to angular 13 and was pretty distraught that everything was breaking left and right. Such a relief to be able to fix them all easily.

I don't look forward to replacing all those @Selects in the future, and I will miss using it. But I guess if it must be, it must be. When making this change, though, please give lots of examples of how to map different uses of @Select to store.select. I found that the example above: //@Select(UserState) userState$: Observable<UserStateModel>; userState$ = this.store.select<UserStateModel>(UserState); didn't really work for me (given my limited understanding). Thanks

ckapilla avatar Jul 04 '22 04:07 ckapilla

@ckapilla the @Select decorator uses store.select internally. Thus when you do @Select(UserState) it passes all arguments directly to store.select so this becomes store.select(UserState). The issue is it's tightened to a static Store instance; this hasn't been an issue in a single frontend application (w/o SSR and module federation).

SSR apps might be rendered concurrently when multiple HTTP requests are done and the second application being rendered overwrites the static Store property with its instance. This means the first application (which is also being rendered) uses the wrong Store instance (the second app created that). The same issue is with the module federation.

arturovt avatar Jul 04 '22 16:07 arturovt

@ckapilla I did the same same thing at first based off of the docs but here is an example showing what @arturovt last comment explains. Avoid this.store.select<UserStateModel>(UserState);

Convert:
@Select(TransactionsState.transactions) transactions$!: Observable<TransactionData>;

To: transactions$: Observable<TransactionData> = this.store.select(TransactionsState.transactions);

Then you can subscribe or async pipe etc. this.transactions$.subscribe(data => console.log(data));

digitalcraftco avatar Jul 11 '22 15:07 digitalcraftco

After a lengthy discussion, we decided to deprecate the Select decorator since it has impossible bugs to fix (just because Angular itself doesn't allow it). We're also planning to drop it in the future. It's complicated to maintain since we have to invest a lot of time into a single Select decorator rather than focusing on other essential issues. It's not type-safe. It has problems in server-side rendering and module federation. We tried to fix the server-side rendering issue initially, leading to a dead end.

For now, start replacing the Select decorator with store.select. You also shouldn't have any issues with 3.7.4 if you drop Select decorator usages.

There goes my weekend 🤣

image

evkw avatar Jul 12 '22 04:07 evkw

@ckapilla I did the same same thing at first based off of the docs but here is an example showing what @arturovt last comment explains. Avoid this.store.select<UserStateModel>(UserState);

Convert: @Select(TransactionsState.transactions) transactions$!: Observable<TransactionData>;

To: transactions$: Observable<TransactionData> = this.store.select(TransactionsState.transactions);

@digitalcraftco thanks so much for those details, looking forward to giving it a try.

ckapilla avatar Jul 12 '22 14:07 ckapilla

Is there some confusion re Avoid this.store.select<UserStateModel>(UserState); ? Aren't we to avoid the @Select decorator and not the generic version of store.select<T>()? Prob the best option is actually the select<T>(selector: StateToken<T>): Observable<T> for type safety.

jaibeales avatar Jul 12 '22 19:07 jaibeales

@jaibeales I think that makes the most sense. The confusion is arising from us who don't fully understand whats happening behind the scenes of the @Select decorator in the first place and what the correct (official) path is to convert them all over. I was running into a lot cannot read properties errors when trying convert @Select like below. Maybe you have some better insight for us?

animals$: Observable<string[]>; this.animals$ = this.store.select(state => state.zoo.animals);

someState$ = this.store.select<SomeStateModel>(SomeState);

digitalcraftco avatar Jul 12 '22 20:07 digitalcraftco

Small tip for changing the @Select

In Vscode, you can regex @Select\((.*)\)\n(.*): Observable<(.*)>;$ and replace by $2 = this.store.select<$3>($1);

lehmstedt avatar Jul 14 '22 20:07 lehmstedt

We got this error in one component after upgrading but found it was due to a service that was being provided locally to a component in the providers array when it didn't need to be. Changing it to be global via the providedIn: 'root' option fixed it for now. We only have about ~100 uses of Select in our app, but since none of them are currently breaking on 3.7.4 will we need to worry about this change? Or is this just for applications that use SSR/universal?

johnhwright avatar Aug 02 '22 16:08 johnhwright

Our recommendation to move away from the decorator still stands but we decided to only introduce the deprecation message in v3.8 because we will be providing a cleaner alternative to the decorator that does not require injecting the store. Keeping this issue open until this alternative and deprecation message is released.

markwhitfeld avatar Aug 08 '22 22:08 markwhitfeld

great news -- much appreciate your coming up with a cleaner solution

ckapilla avatar Aug 09 '22 01:08 ckapilla

Just for a bit of a preview of what we are looking at, here is a prototype I made last year: https://stackblitz.com/edit/ngxs-select-dispatch-utils-v0-bmecrf?file=src%2Fapp%2Fapp.component.ts

It is in Angular 11, so there may be a small tweak around the inject function based on the Angular version you are using. I have had some customers drop this utility in their codebase already and are loving it! We are certain that we would be adding the select utility that you see here, potentially the dispatch utility (depending on how much feedback we get on the API for it) and most likely not the selectSnapshot utility (it is not particularly performant in its current form).

markwhitfeld avatar Aug 10 '22 20:08 markwhitfeld

Nice @markwhitfeld! Lovely simple functions ♥. I hadn't heard of ɵɵdirectiveInject(), using this from a function to get dependencies rather than injecting via constructor is very powerful (although somewhat experimental?).

jaibeales avatar Aug 12 '22 11:08 jaibeales

Would it be worth updating the select docs with a note on the deprecation, with the alternative approach highlighted e.g for people who aren't aware and start on a new project for the first time

briancodes avatar Nov 14 '22 10:11 briancodes

@markwhitfeld utility functions seem to work great in Angular 15 with inject() instead of private ɵɵdirectiveInject(): https://stackblitz.com/edit/ngxs-select-dispatch-utils-v0-fa8hnc?file=src%2Fapp%2Fapp.component.ts

hakimio avatar Feb 16 '23 09:02 hakimio

i see the first draft for the changes in version 3.8 so I think this is a work in progress and it soon will be resolved, thanks to the whole team that is putting a lot of effort on this.

luishcastroc avatar Mar 24 '23 16:03 luishcastroc

I'm trying to migrate to ngxs and my karma tests are experiencing this weird issue. I've removed all @select from my services (I only test those ATM) and the issue is still there. I can create a branch of my repo if it will help anyone, I don't think I can create a minimal reproduction as I have no clue what's causing this. I'm using version 3.8, and @select is not deprecated there as far as I've seen. Is the plan to deprecate it still on motion?

Error: NG0205: Injector has already been destroyed.
error properties: Object({ code: 205 })
Error: NG0205: Injector has already been destroyed.

HarelM avatar May 07 '23 18:05 HarelM

@HarelM could you create a new issue and include a full reproduction? Please include all required information in the issue template when you log it. This issue looks like a different one.

markwhitfeld avatar May 07 '23 18:05 markwhitfeld

I think it was an error on my end unfortunately. I managed to fix the issue by making sure the test is executing the setTimeout I had on my service. Thanks for the super quick response! So this still leaves the question if @Select will be deprecated in the future. And another one, which is related to my issue, is if it's OK to dispatch from a @Select subsription? In angular-redux which I currently use it creates a problem of state change fires incorrectly, so I needed to add a setTimeout. Is this needed here?

HarelM avatar May 07 '23 18:05 HarelM

The @Select decorator will be deprecated, but will be replaced with a simpler alternative (see updated example https://github.com/ngxs/store/issues/1854#issuecomment-1432779613). We are busy fleshing out the RFC for this API with respect to signals vs observables.

You can dispatch in response to a select, and it will work as expected, but I would recommend looking at your actions to see if there is something there that you could respond to.

On Sun, 07 May 2023, 20:42 Harel M, @.***> wrote:

I think it was an error on my end unfortunately. I managed to fix the issue by making sure the test is executing the setTimeout I had on my service. Thanks for the super quick response! So this still leaves the question if @select https://github.com/select will be deprecated in the future. And another one, which is related to my issue, is if it's OK to dispatch from a @select https://github.com/select subsription? In angular-redux which I currently use it creates a problem of state change fires incorrectly, so I needed to add a setTimeout. Is this needed here?

— Reply to this email directly, view it on GitHub https://github.com/ngxs/store/issues/1854#issuecomment-1537514462, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAO3U2KC565GNSK66OEJLQTXE7UKHANCNFSM5YK4NAUQ . You are receiving this because you were mentioned.Message ID: @.***>

markwhitfeld avatar May 08 '23 05:05 markwhitfeld

The main reason I'm asking is because I had a bug when using angular-redux with the same flow: https://github.com/IsraelHikingMap/Site/issues/1749#issuecomment-1227120529 I'm not sure I know how to solve this without updating the state from a state change event. My flow is as follows:

  1. I get an event from the GPS position
  2. I store this location in the state (so it can be shared and subscribed to)
  3. I listen to location state changes in order to add it to a recording line -> which updates the state with the recorded line

I do all the logic outside the state classes and leave them to be simple, without logic, just update state from the action data, so I'm not sure how to avoid this "loop".

HarelM avatar May 08 '23 21:05 HarelM