4

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 ?

ufk
  • 30,912
  • 70
  • 235
  • 386

4 Answers4

4

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

Chellappan வ
  • 23,645
  • 3
  • 29
  • 60
  • 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()`. – BizzyBob May 04 '23 at 03:15
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);
    }
}
Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
2

In the v16+ use runInInjectionContext

injector = inject(EnvironmentInjector);

or

constructor(private injector: EnvironmentInjector)

and then

runInInjectionContext(this.injector, snackMsgSuccess);
angularrocks.com
  • 26,767
  • 13
  • 87
  • 104
0

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/*"]

julianpoemp
  • 1,965
  • 3
  • 14
  • 29