properly understanding inject in angular14 - inject() must be called from an injection context
Asked Answered
V

6

7

I'm trying to learn the changes in angular 14, especially the inject() feature where i'm able to inject modules to functions and i don't need to create special services for that.. but i think i got something wrong.

I'm trying to create some static functions to send snack messages using the package ngx-toastr, but this package is not relevant to my question. how do I properly implement functions that show snack messages while injecting to them the required modules that they need to operate.

this is my messages.ts file:

import {inject} from '@angular/core';
import {ToastrService} from 'ngx-toastr';


export const snackMsgSuccess = (msg: string, title?: string) => {
  const toaster = inject(ToastrService);
  toaster.success(msg, title, {
    easeTime: 1000
  });
};


export const snackMsgInfo = (msg: string, title?: string) => {
  const toaster = inject(ToastrService);
  toaster.info(msg, title, {
    easeTime: 1000
  });
};

export const snackMsgWarn = (msg: string, title?: string) => {
  const toaster = inject(ToastrService);
  toaster.warning(msg, title, {
    easeTime: 1000
  });
};


export const snackMsgError = (msg: string, title?: string) => {
  const toaster = inject(ToastrService);
  toaster.error(msg, title, {
    easeTime: 1000
  });
};

and I got the following error:

Error: Uncaught (in promise): Error: NG0203: inject() must be called from an injection context (a constructor, a factory function or a field initializer)

well... i had a problem before when i tried to have a supporting function to get route params:

export const routeParam$ = (key: string) => {
  const activatedRoute = inject(ActivatedRoute);

  return activatedRoute.params.pipe(
    pluck(key),
    filter(r => r !== null),
    distinctUntilChanged()
  );
};

and i was only able to use as an field initializer in a component with task: Observable<string> = routeParam$('task');

well the error message is very clear... but still.. i'm new to angular14 and i thought that inject would allow me to do that. it's not that useful for me otherwise.

for now I moved it as a service..

import {Injectable} from '@angular/core';
import {ToastrService} from 'ngx-toastr';

@Injectable({
  providedIn: 'root'
})
export class MsgService {

  constructor(private toaster: ToastrService) {
  }

  public snackMsgSuccess = (msg: string, title?: string) => {
    this.toaster.success(msg, title, {
      easeTime: 1000
    });
  };


  public snackMsgInfo = (msg: string, title?: string) => {
    this.toaster.info(msg, title, {
      easeTime: 1000
    });
  };

  public snackMsgWarn = (msg: string, title?: string) => {
    this.toaster.warning(msg, title, {
      easeTime: 1000
    });
  };


  public snackMsgError = (msg: string, title?: string) => {
    this.toaster.error(msg, title, {
      easeTime: 1000
    });
  };
}

but is this the only way to implement it ? even in angular14 ?

Vista answered 5/6, 2022 at 22:17 Comment(0)
A
6

As mentioned in the Answer, It can be only initialised during instantiation of a dependency by the DI system. You can workaround this by creating higher order function.

export const snackMsgSuccess = () => {
  const toaster = inject(ToastrService);
  return (msg: string,title?: string)=>{
    toaster.success(msg, title, {
      easeTime: 1000
    });
  }
};

component.ts

snackMsgSuccess = snackMsgSuccess();


ngOnInit(){
   this.snackMsgSuccess('Success','Test');
}

Update Angular 14.1

In this version inject function can be used inside function body using runInContext API.

For More Info

Amaze answered 6/6, 2022 at 3:22 Comment(1)
I like this solution better than using runInContext because consumers don't need to inject the injector, then call the runInContext(...). Assigning a local variable does the same thing, but allows the usage to be simpler IMO. Prepending the function name with "inject" might be a good convention to make it obvious that execution must occur in the constructor (or "on the class"). snackMsgSuccess = injectSnackMsgSuccess().Tomasine
I
2

Since Angular 14.1 you can use the runInContext :

Simple example:

class Foo {
    constructor(private injector: EnvironmentInjector) {
        setTimeout(() => {
            // can't inject()

            this.injector.runInContext(() => {
                inject(MyService) // fine
            });
        }, 1000);
    }
}
Ischium answered 26/9, 2022 at 19:27 Comment(0)
A
2

In the v16+ use runInInjectionContext

injector = inject(EnvironmentInjector);

or

constructor(private injector: EnvironmentInjector)

and then

runInInjectionContext(this.injector, snackMsgSuccess);
Angadresma answered 25/3, 2023 at 4:8 Comment(0)
B
2

I had the same issue for one of my Angular projects using Nx and Angular 15 without breaking this rule. I got it solved adding the following line to compilerOptions.paths in the tsconfig.base.json file:

"@angular/*": ["node_modules/@angular/*"]

Byrom answered 8/7, 2023 at 15:55 Comment(2)
could you explain what this does?Quade
Yes, it's working for me. I was facing this issue while upgrading my angular version from 14 to 16. I have added this line in my compilerOptions.paths, now it's working.Neck
H
2

One reason why you may receive this error is because you are dependent on a library that depends on a different version of Angular than your main package does, and you are importing that library's module into your Angular module.

You should be able to determine this by looking at node_modules/some_library/node_modules, and seeing if @angular libraries are present there.

This answer is a workaround for that. The proper solution is to make sure your main package and the library you depend on are using the same versions of @angular packages. One way to do this is to make sure you have the same major version, and use ^ as your semver prefix for @angular packages in your package.json.

Hurling answered 22/2 at 16:38 Comment(0)
S
1

This Problem about Version if you use angular 17.2 and your library 17.3. Absolutly you can get this error

Sorely answered 2/4 at 7:27 Comment(2)
It's difficult to understand your answer, can you edit it to add more details?Mikkanen
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Martin

© 2022 - 2024 — McMap. All rights reserved.