Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

defer loading a component with NgModule with providers throws error #52876

Closed
naveedahmed1 opened this issue Nov 13, 2023 · 19 comments
Closed

defer loading a component with NgModule with providers throws error #52876

naveedahmed1 opened this issue Nov 13, 2023 · 19 comments
Assignees
Labels
area: core Issues related to the framework runtime core: defer Issues related to @defer blocks. P2 The issue is important to a large percentage of users, with a workaround state: has PR
Milestone

Comments

@naveedahmed1
Copy link
Contributor

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

Yes

Description

When defer loading a component with NgModule with providers throws error:

dashboard-stats.component.ts:76 ERROR NullInjectorError: R3InjectorError[_InjectionService -> _InjectionService]: 
  NullInjectorError: No provider for _InjectionService!
    at NullInjector.get (core.mjs:5601:27)
    at R3Injector.get (core.mjs:6044:33)
    at R3Injector.get (core.mjs:6044:33)
    at ChainedInjector.get (core.mjs:15407:36)
    at lookupTokenUsingModuleInjector (core.mjs:4112:39)
    at getOrCreateInjectable (core.mjs:4160:12)
    at ɵɵdirectiveInject (core.mjs:11970:19)
    at ɵɵinject (core.mjs:917:42)
    at NodeInjectorFactory.TooltipService_Factory [as factory] (swimlane-ngx-charts.mjs:4152:39)
    at getNodeInjectable (core.mjs:4366:44)

Whereas lazyloading same component with import works just fine.

Parent component:

import { Component, OnInit } from '@angular/core';
import { MediaService } from "./../../shared/services/media.service";
import { EmployerDashboardStatsComponent } from './dashboard-stats.component';

@Component({
  templateUrl: './account.component.html',
  standalone: true,
  imports: [
    DashboardStatsComponent
  ]
})
export default class AccountComponent implements OnInit {

  constructor(
    public media: MediaService
  ) { }

  ngOnInit() {

  }


}

Parent component template

@defer (when media.isGTXS) {
<dashboard-stats></dashboard-stats>
}

Child Component


import { HttpClient } from '@angular/common/http';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { LegendPosition, NgxChartsModule, ScaleType } from '@swimlane/ngx-charts';
import * as shape from 'd3-shape';

@Component({
  selector: 'dashboard-stats',
  templateUrl: './dashboard-stats.component.html',
  standalone: true,
  imports: [
    NgxChartsModule
  ],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardStatsComponent implements OnInit {

  //charts data
  public jobResponses: any[] = [];
  public experienceLevels: any[] = [];
  public recruitmentActivity = [];

  // options
  public view: [number, number] = [360, 220];
  public showXAxis = true;
  public showYAxis = true;
  public gradient = false;
  public showLegend = false;
  public legendPosition = LegendPosition.Below;
  public showXAxisLabel = true;
  public showYAxisLabel = true;
  public colorScheme = "vivid";

  // pie
  public showLabels = false;
  public explodeSlices = false;
  public doughnut = false;
  public pieChartColorScheme = {
    name: 'vivid',
    selectable: true,
    group: ScaleType.Ordinal,
    domain: ['#e1f5fe', '#b3e5fc', '#81d4fa', '#4fc3f7', '#29b6f6', '#03a9f4', '#039be5', '#0288d1', '#0277bd', '#01579b']
  };

  //line
  public lineChartLineInterpolation = shape.curveBasis;

  constructor(
    private http: HttpClient,
    private cdr: ChangeDetectorRef
  ) {
  }

  ngOnInit() {

  }

  axisFormat(val) {
    if (val % 1 === 0) {
      return val.toLocaleString();
    } else {
      return '';
    }
  }
}

Dynamically loading component works perfectly:

 //lazylod dashboard-stats component
 import('./dashboard-stats.component').then((c) => {
   const refStats = this.viewContainerStatsContainer.createComponent(c.EmployerDashboardStatsComponent);
 });

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

dashboard-stats.component.ts:76 ERROR NullInjectorError: R3InjectorError[_InjectionService -> _InjectionService]: 
  NullInjectorError: No provider for _InjectionService!
    at NullInjector.get (core.mjs:5601:27)
    at R3Injector.get (core.mjs:6044:33)
    at R3Injector.get (core.mjs:6044:33)
    at ChainedInjector.get (core.mjs:15407:36)
    at lookupTokenUsingModuleInjector (core.mjs:4112:39)
    at getOrCreateInjectable (core.mjs:4160:12)
    at ɵɵdirectiveInject (core.mjs:11970:19)
    at ɵɵinject (core.mjs:917:42)
    at NodeInjectorFactory.TooltipService_Factory [as factory] (swimlane-ngx-charts.mjs:4152:39)
    at getNodeInjectable (core.mjs:4366:44)


### Please provide the environment you discovered this bug in (run `ng version`)

```true
Angular CLI: 17.0.0
Node: 18.18.2
Package Manager: npm 9.8.1
OS: win32 x64

Angular: 17.0.2
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, platform-server
... router, service-worker

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1700.0
@angular-devkit/build-angular   17.0.0
@angular-devkit/core            17.0.0
@angular-devkit/schematics      17.0.0
@angular/cdk                    17.0.0
@angular/cli                    17.0.0
@angular/fire                   16.0.0
@angular/google-maps            17.0.0
@angular/material               17.0.0
@angular/pwa                    17.0.0
@schematics/angular             17.0.0
rxjs                            7.8.1
typescript                      5.2.2
zone.js                         0.14.0

Anything else?

No response

@AndrewKushnir AndrewKushnir added needs reproduction This issue needs a reproduction in order for the team to investigate further area: core Issues related to the framework runtime core: defer Issues related to @defer blocks. labels Nov 13, 2023
@ngbot ngbot bot modified the milestone: Backlog Nov 13, 2023
@AndrewKushnir
Copy link
Contributor

@naveedahmed1 thanks for reporting the issue. Could you please provide a minimal repro as well (for example, using https://stackblitz.com/), so that we can investigate it further?

@naveedahmed1
Copy link
Contributor Author

@JeanMeche
Copy link
Member

Looks like this is an issue with the library swimlane/ngx-charts#1733

@naveedahmed1
Copy link
Contributor Author

Thank you @JeanMeche for quick reply. I have also added the code which lazily loads the component using import statement. if you could please try uncommenting it and see it works perfectly.

@JeanMeche
Copy link
Member

JeanMeche commented Nov 13, 2023

Something is messing up the providers. Having importProvidersFrom([NgxChartsModule]) in your application providers fixes the issue.

I'll let Andrew confirm or deny, but this looks like a poor implementation to me. The service is provided by a nested module.

flowchart RL
    Z[NgxChartsModule] --> |imports|A
    A[ChartCommonModule ] -->|imports|C[TooltipModule]
    C --> |provides|InjectionService

@JeanMeche
Copy link
Member

I've got a smaller repro : https://stackblitz.com/edit/angular-or9ksy

Here the injection is broken when the MyModule import is in the defered component, but it works fine if it is in the AppComponent

@naveedahmed1
Copy link
Contributor Author

naveedahmed1 commented Nov 14, 2023

Thank you @JeanMeche for the update.

The service is provided by a nested module.

The service is Tooltip Service, right? if so I think its normal for TooltipModule to provide this service, isn't it?

Dynamically creating component using ViewContainerRef.CreateComponent works fine, the issue arises only if we put this component in defer block.

@AndrewKushnir AndrewKushnir added P2 The issue is important to a large percentage of users, with a workaround and removed needs reproduction This issue needs a reproduction in order for the team to investigate further labels Nov 14, 2023
@AndrewKushnir
Copy link
Contributor

@naveedahmed1 thanks for additional information. I've reproduced the problem and will work on a fix. Will keep this thread updated.

AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Nov 14, 2023
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Nov 14, 2023
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
@AndrewKushnir AndrewKushnir self-assigned this Nov 14, 2023
AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Nov 14, 2023
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Nov 15, 2023
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
@naveedahmed1
Copy link
Contributor Author

@AndrewKushnir will this fix be a part of v17.0.4?

@AndrewKushnir
Copy link
Contributor

will this fix be a part of v17.0.4?

@naveedahmed1 no, the fix will be released later, since it needs a bit more research. As a possible temporary solution, you can try using the importProvidersFrom([NgxChartsModule]) in your application providers at bootstrap time or at a particular route level.

@michaelfaith
Copy link

Very different circumstances, but similar error as this provider / injection issue I reported from v16. Wonder if they're related in some way (#51070)

@wariomvc
Copy link

Same issue, only error when use defer

NullInjectorError: R3InjectorError(_AppModule)[InjectionToken mat-select-scroll-strategy -> InjectionToken mat-select-scroll-strategy]:
NullInjectorError: No provider for InjectionToken mat-select-scroll-strategy

@kkachniarz220
Copy link

Same issue here with CurrencyPipe.
I have custom pipe to format price. I'm using ngModule to provide CurrencyPipe and export my custom pipe.
Defer components throws an error with ERROR Error: R3InjectorError(Environment Injector)[CurrencyPipe -> CurrencyPipe].

Reproduction: https://stackblitz.com/edit/stackblitz-starters-wbseoe?file=src%2Fmain.ts

@JeanMeche
Copy link
Member

@kkachniarz220 injecting pipe is a bad practice, you should rely on the underlying formatCurrency

export class CustomPipe implements PipeTransform {
  locale = inject(LOCALE_ID);

  transform(value: number, currency = 'EUR'): string | null {
    return formatCurrency(value, this.locale, currency, 'symbol', '1.0-2')
  }
}

@kkachniarz220
Copy link

kkachniarz220 commented Jan 9, 2024

Hmm, why it's a bad practice? Angular CurrencyPipe source code have a bit of logic so I don't want to duplicate it.
In addition I can`t inherit because of different fn arguments (example in reproduction is simplified)

@JeanMeche
Copy link
Member

JeanMeche commented Jan 9, 2024

@kkachniarz220
Please have a look at this comment:

One thing that should be noted is that providing a service on a NgModule level is roughly equivalent of providing a service via @Injectable(providedIn: 'root'). In other words - providers are not scoped to a NgModule (this is a common misconception I see) but rather all providers are folded into the root injector (!). So you could just register the desired pipe on the root / when bootstrapping an application - this would be equivalent to registering it on a NgModule, really.

Then, pipes are meant to be used as a formatting facility in a template, not as generic providers. We never intended pipes to act as providers. In the particular case of the DatePipe you would be better off reusing the formatDate function directly.

I'm going to close this one as I don't think we want to introduce providers on a pipe level. The NgModule equivalent behaviour can be achieved today without NgModules and an alternative (arguably better) solution exists for the DatePipe

AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Feb 2, 2024
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Feb 2, 2024
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Feb 2, 2024
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Feb 13, 2024
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue Feb 23, 2024
…block

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves angular#52876.
dylhunn pushed a commit that referenced this issue Feb 23, 2024
…block (#52881)

Currently, when a `@defer` block contains standalone components that import NgModules with providers, those providers are not available to components declared within the same NgModule. The problem is that the standalone injector is not created for the host component (that hosts this `@defer` block), since dependencies become defer-loaded, thus no information is available at host component creation time.

This commit updates the logic to collect all providers from all NgModules used as a dependency for standalone components used within a `@defer` block. When an instance of a defer block is created, a new environment injector instance with those providers is created.

Resolves #52876.

PR Close #52881
@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented Feb 23, 2024

FYI, this issue should be fixed by 5712352, the fix will be released as a part of v17.2.3 next week. Please reopen (or create a new ticket) if the problem still exists after updating to v17.2.3.

@naveedahmed1
Copy link
Contributor Author

Thank you so much @AndrewKushnir :)

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Mar 25, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: core Issues related to the framework runtime core: defer Issues related to @defer blocks. P2 The issue is important to a large percentage of users, with a workaround state: has PR
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants
@JeanMeche @naveedahmed1 @michaelfaith @wariomvc @AndrewKushnir @kkachniarz220 and others