Core: Error: No provider for TranslatePipe!

Created on 6 Jul 2017  路  20Comments  路  Source: ngx-translate/core

I'm submitting a ... (check one with "x")

[x] bug report => check the FAQ and search github for a similar issue or PR before submitting
[ ] support request => check the FAQ and search github for a similar issue before submitting
[ ] feature request

Current behavior

When trying to inject TranslatePipe (in another Pipe, as an helper), I get the error "Error: No provider for TranslatePipe!"

Expected/desired behavior

We should be able to either inject the Pipe, or have a service/proper Injectable doing the similar heavy lifting to allow easier integration with custom pipes

Reproduction of the problem
http://plnkr.co/edit/Yjh9qLSzLSfCWI77xW3f?p=info

What is the motivation / use case for changing the behavior?
Chaining pipes is nice, but troublesome. being able to have a single pipe taking care of everything would be much better. If we can't, we will ALWAYS end up doing 'my-value' | customPipe | translate. It's a pain.

Please tell us about your environment:

  • ngx-translate version: 7.0.0
  • Angular version: 4.1.3
  • Browser: all

PS: I consider this a bug as it's been advised previously to inject the pipe in a service or another pipe. Plus the pipe is tagged as @Injectable, so I expect to be able to inject it.

Most helpful comment

One more way

import { TranslatePipe } from "@ngx-translate/core";

@Component({
    selector: '',
    templateUrl: '',
    providers: [TranslatePipe]
})

All 20 comments

Did you find any workaround to this @lemoinem?

The workaround I found was to use the two pipes separately. I've not been able to integrate and streamline the two pipes as one.

I've created my own provider for the TranslatePipe

import { ChangeDetectorRef } from '@angular/core';
import { TranslateModule, TranslateLoader, TranslateService, TranslatePipe } from "@ngx-translate/core";


export let translatePipeProvider = {
    provide: TranslatePipe,
        useFactory: (translateService: TranslateService, changeDetectorRef: ChangeDetectorRef) => {
        return new TranslatePipe(translateService, changeDetectorRef)
    },
    deps: [TranslateService, ChangeDetectorRef]
};

and then registered it in my component

import { translatePipeProvider } from 'path/to/provider';

@Component({
    selector: '',
    templateUrl: '',
    providers: [translatePipeProvider]
})

I'm a bit worried about internal state and clean up...
The translate Pipe is storing loads of data (last key, last value). If it's used as a bare service, wouldn't this trigger many useless Detection Change cycles?

Even when registered at the component level?
I'm thinking there are still points I don't understand regarding Pipes and DI...

One more way

import { TranslatePipe } from "@ngx-translate/core";

@Component({
    selector: '',
    templateUrl: '',
    providers: [TranslatePipe]
})

@samoilenko how do you use your own provider for the pipe? I am having the same issue

I'm using Angular 4, if you provide the pipe in the module, you can then import it and use it within another pipe where needed.

I'm going to close this issue. I have gained a better understanding of how Pipes are managed in Angular2+. For my use case (implicitly calling the TranslatePipe from another Pipe), I arrived at the conclusion that the best solution would be to:

  1. Inject the TranslatePipe's dependencies in my own pipe
  2. Instantiate a new instance of the TranslatePipe in my own pipe's transform
  3. Destroy the TranslatePipe's instance in my own pipe's onDestroy

Note: Of course, my custom pipe needs to be impure since the TranslatePipe is.

The issue being that if the TranslatePipe's dependencies change, I will have to update my custom pipe as well.
This issue is not easily solved and would basically required Angular2+ to manage dependencies between pipes themselves.
I haven't open a feature request for this (nor have I looked if one already existed) as this isn't something that is major problem for me now.

Creating a normal provider for the TranslatePipe will not work and might have horrible performance implications for your app along, with some translations not changing when the language is changed.

I still think in the meantime the Pipe should not be marked as Injectable anymore and we should stop advising to inject it in Services or other Pipes.

Why can't we declare this provider at the app level ?
Why must I declare it at each component ? What kind of design is that ?

What makes you think that you couldn't declare it at app level @RoyiNamir ?

@ocombe Because I did this :

in core.module.ts (core (!) - which uploads to the root injector) :

providers   : [       ...other...  , translatePipeProvider]

Where translatePipeProvider is : (from this thread ^ )

import {ChangeDetectorRef}               from '@angular/core';
import {TranslatePipe, TranslateService} from "@ngx-translate/core";

export  const translatePipeProvider = {
    provide   : TranslatePipe,
    useFactory: (translateService: TranslateService, changeDetectorRef: ChangeDetectorRef) =>
    {
        return new TranslatePipe(translateService, changeDetectorRef);
    },
    deps      : [
        TranslateService,
        ChangeDetectorRef
    ]
};

Then I inject it to my component :

export class PaymentMethodsComponent extends BaseComponent implements OnInit
    {
      constructor(,,,  , private _TranslatePipe:TranslatePipe)
        {    }

And when I test it via :

  console.log(this._TranslatePipe.transform("Resources.loginSms_lblWeSent" , null));

I get :

 ERROR  ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[TranslatePipe -> ChangeDetectorRef]:
JS:   StaticInjectorError(Platform: core)[TranslatePipe -> ChangeDetectorRef]:
JS:     NullInjectorError: No provider for ChangeDetectorRef!
JS: Error: NullInjectorError: No provider for ChangeDetectorRef!
JS:     at _NullInjector.get (file:///data/data/com.davidshield.dsapp1/files/app/tns_modules/@angular/core/bundles/core.umd.js:1041:19) [angular]

HOWEVER

If I do this ( adding `providers:[TranslatePipe] at the component level :

image

image

So ?

Oh yes it makes sense, it's because ChangeDetectorRef is only available in a component, it's what triggers change detection when the template is updated. This is the same code as what you can find in the AsyncPipe for example: https://github.com/angular/angular/blob/c8a1a14b87e5907458e8e87021e47f9796cb3257/packages/common/src/pipes/async_pipe.ts#L78

I don't really understand why you would want to provide inject the TranslatePipe yourself though, it will not work without the TranslateService anyway. Just import the TranslateModule and you will have access to the TranslatePipe in your components. If you need to get access to translations in your service, then use the TranslateService, it'll do the same thing that the TranslatePipe, but it's meant to be used in your code (which is not the case for the pipe that you're supposed to use in a template).

Do you have a specific use case?

@ocombe Ok - let me clarify :

Im talking about TS usages only ( not HTML pipe).
Until yesterday I was doing :

ctor : private translateService:TranslateService

and then

translateService:TranslateService.get(...).pipe(first()) .subscribe( ....get data)

And it worked.

However - I hate the subscribe mechanism ( just to get a resource , I don't(!) need) to listen to subscriptions).

And then a colleague told me that I can run the pipe code myself , as it's in the html. ( without observable)

So basically to inject the translatepipe , and run it _instead_ of the observable code.

This is what i've tried ^ and asked here in my first question..

Anyway I've also tried ( what you've said) :

At the app.module I load it via :

So I load it like this in core module :

export const GetTranslateModuleLoaderFORROOT: ModuleWithProviders = TranslateModule.forRoot({
                                         loader: {
                                                            provide : TranslateLoader,
                                                            useClass: MyTranslateLoaderService 
                                                     }
                                                 });

And this is how I load it in the app.module :

image

And that's it. ( it did load the module and its services to the root injector , right?)

So now - my class looks like :

image

But now I get ,

JS:  ERROR  ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[PaymentMethodsComponent -> TranslatePipe]:
JS:   StaticInjectorError(Platform: core)[PaymentMethodsComponent -> TranslatePipe]:
JS:     NullInjectorError: No provider for TranslatePipe!
JS: Error: NullInjectorError: No provider for TranslatePipe!

Just like the issue of this thread ^.

So How can I use the translatepipe in TS ? (without injecting it to each component)

Worth to mention , that If I now inject it _as a provider to the component_( in the last image ^) - IT DOES WORK..

So , now you see why I've asked my question above ^:

Why not use the translate service instant method instead which is synchronous and doesn't use an observable?

And I replied to your questions, you can't declare it at app level because the pipe uses ChangeDetectorRef which is for components and directives.
What kind of design is this? The one from the Angular team.

@ocombe Sure , I've already understood your answer about changeDetectorRef.
I haven't noticed that it uses the changeDetectorRef - hence must be injected at each component.

So my question was basically wrong.

More - It's the first time I hear about the instant method. (happens, right? :-))

I thank you for your answer and knowledge. :)
Thanks again.

@samoilenko thanks!

This thing is working correctly with below code in angular 7 I am posting this for those who might face this issue in angular 7.

In module where you using this pipe
import { CommonModule, I18nPluralPipe } from '@angular/common';

and add your pipe in providers of that module using

providers: [

    I18nPluralPipe
],

and in your custom pipe inject the pipe whichever you want to use in my case i am using I18nPluralPipe.
constructor(
private i18nPluralPipe: I18nPluralPipe
) {}

and then called transform using

this.i18nPluralPipe.transform(value,Plural);

This thing is working correctly with below code in angular 7 I am posting this for those who might face this issue in angular 7.

In module where you using this pipe
import { CommonModule, I18nPluralPipe } from '@angular/common';

and add your pipe in providers of that module using

providers: [

],

and in your custom pipe inject the pipe whichever you want to use in my case i am using I18nPluralPipe.
constructor(
private i18nPluralPipe: I18nPluralPipe
) {}

and then called transform using

this.i18nPluralPipe.transform(value,Plural);

No this doesn't works with angular7

I also faced the same issue. This is how I solved:

@Pipe({ name: 'customPipe', pure: false })
export class CustomPipe implements PipeTransform {
    translatePipe: TranslatePipe;

    constructor(translate: TranslateService, _ref: ChangeDetectorRef) {
        this.translatePipe = new TranslatePipe(translate, _ref);
    }

    transform(value: any): string {

        return `${this.translatePipe.transform(value)}`;
    }
}

Notice that I followed @lemoinem 's solution, but in my case I had to set the pure flag of the pipe false. Because in my case it wasn't detect the changes (when user was selecting another language from a drop down).

You need to make the pipe aware of language changes. See the pseudocode below (I haven't run it but hopefully it can lead you in the right direction). It's based on how the translate pipe is implemented:

@Pipe({
  name: 'customTranslate',
  pure: false
})
export class CustomTranslate implements PipeTransform, OnDestroy {
  private langChangeSubscription: Subscription;
  private value: string;
  private lastQuery: string;
  private lastParams: any;
  private lastLang: string;

  constructor(private translate: TranslateService, private cdr: ChangeDetectorRef) {
    this.langChangeSubscription = translate.onLangChange.pipe(
      takeUntil(this.destroy$)
    ).subscribe(() => {
      this.cdr.markForCheck();
    });
  }

  ngOnDestroy() {
    this.langChangeSubscription.unsubscribe();
  }

  transform(query: string, params: any): string {
    if (equals(query, this.lastQuery) && equals(params, this.lastParams) && equals(this.translate.currentLang, this.lastLang)) {
      return this.value;
    }
    this.lastQuery = query;
    this.lastParams = params;
    this.lastLang = this.translate.currentLang;
    this.translate.get(query, params).subscribe((result) => {
      this.value = result;
      this.cdr.markForCheck();
    });
  }
}
Was this page helpful?
0 / 5 - 0 ratings