Issue
Recently I had a task where I needed to pass a TemplateRef to component, which provides some service, say SomeService. And inside that template I used a directive that injects service SomeService. I ended up with NullInjectorError.
So my question is how can I get service provided in component but not inside ViewContent.
Here is minimal representation of what I'm trying to do. Let's assume that all components and directives are declared in the same module.
root-component. All that's happening here is that I'm passing a template reference to the app-component and use the directive inside template.
@Component({
selector: 'root-component',
template: `
<app-component [itemTemplate]="template"></app-component>
<ng-template #template >
<div testDirective>hello</div>
</ng-template>
`
})
export class RootComponent {
}
app-component. Here I provide SomeService
@Component({
selector: 'app-component',
template: `
<ng-container [ngTemplateOutlet]="itemTemplate"></ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
SomeService // <-- HERE IS THE SERVICE THAT I'M TRYING TO INJECT INTO DIRECTIVE
],
})
export class AppComponent {
@Input()
itemTemplate: TemplateRef<unknown> | null = null;
constructor(
) {
}
}
testDirective
@Directive({
selector: '[testDirective]'
})
export class TestDirective {
constructor(
private _someService: SomeService // <-- THIS CAUSE AN NullInjectorError
) {
}
}
There is no need to show the implementation of SomeService as it is just a regular service.
I apologize if something is not clear, this is my first question on StackOverflow
Solution
Angular v14 solution
With Angular 14, you can pass injector to ngTemplateOutlet directive. And by that way, the embeddedView that is going to render inside ng-container will refer to the current component injector tree (and point to injected SomeService provider on the component level.
@Component({
selector: 'app-component',
template: `
<ng-container
[ngTemplateOutlet]="itemTemplate"
[ngTemplateOutletInjector]="injector">
</ng-container>
`,
...
})
export class AppComponent {
@Input()
itemTemplate: TemplateRef<unknown> | null = null;
constructor(
public readonly injector: Injector
) {}
}
Angular v13 or less
You have to make some changes in the way the template has been passed. Rather than passing TemplateRef as Input binding. Pass that ng-template as a Content Projection.
@Component({
selector: 'root-component',
template: `
<app-component>
<!--Pass template as content projection-->
<ng-template #template>
<div testDirective>hello</div>
</ng-template>
</app-component>
`,
})
export class RootComponent {}
And then AppComponent can use @ContentChild to lookup for that transcluded view (ng-template) thing checking #template template variable. And that's it. itemTemplate will fill in as soon as ContentChild query retrieves the result. Boom 🎉🎉
@Component({
selector: 'app-component',
...
})
export class AppComponent {
// VVVV change here to ContentChild
@ContentChild('template')
itemTemplate: TemplateRef<unknown>;
}
In this case, the template is a part of AppComponent, and the injector tree resolves the SomeService correctly.
Answered By - Pankaj Parkar
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.