[英]Angular inject a component into an attribute directive
Tl;dr : How do I provide a visible component as a dependency for a directive? Tl; dr :如何提供可见组件作为指令的依赖项? Naturally the component has to get initialized before the directive, but it has to be the same instance that gets displayed when the app later runs across the
selector
of the component.自然地,组件必须在指令之前初始化,但它必须是应用程序稍后在组件的
selector
上运行时显示的相同实例。
Details:细节:
My app.component.html has a structure like so:我的 app.component.html 具有如下结构:
app.component.html应用程序组件.html
<app-navigation></app-navigation>
<router-outlet></router-outlet>
There is a navigation bar at the top which is always visible.顶部有一个导航栏,始终可见。 The
<router-outlet>
always displays the currently active component. <router-outlet>
始终显示当前活动的组件。
I'd now like to allow the components that are rendered in the <router-outlet>
to modify the contents of the navigation bar, for example to display additional buttons that fit the currently active component.我现在希望允许在
<router-outlet>
中呈现的组件修改导航栏的内容,例如显示适合当前活动组件的附加按钮。 This should work with a directive, like so:这应该与指令一起使用,如下所示:
some.component.html some.component.html
<div *appTopBar>
<button>Additional Button</button>
</div>
The additional button should now appear in the navigation bar at the top.附加按钮现在应该出现在顶部的导航栏中。
The appTopBar
directive looks like this: appTopBar
指令如下所示:
top-bar.directive.ts top-bar.directive.ts
import {AfterViewInit, Directive, OnDestroy, TemplateRef} from '@angular/core';
import {AppComponent} from '../navigation/navigation.component';
@Directive({
selector: '[appTopBar]'
})
export class TopBarDirective implements AfterViewInit, OnDestroy {
constructor(private tmpl: TemplateRef<any>,
private nav: NavigationComponent) {
}
ngAfterViewInit(): void {
this.nav.setTopBarContent(this.tmpl);
}
ngOnDestroy(): void {
this.nav.setTopBarContent(null);
}
}
The directive has a dependency on the NavigationComponent and can pass content to the navigation bar via the publicly provided methods setTopBarContent()
:该指令依赖于 NavigationComponent 并且可以通过公开提供的方法
setTopBarContent()
将内容传递到导航栏:
navigation.component.ts导航.component.ts
import {Component, EmbeddedViewRef, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
@Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss']
})
export class NavigationComponent {
@ViewChild('topBarContainer',{static: false})
topBar: ViewContainerRef;
topBarContent: EmbeddedViewRef<any>;
constructor() {}
/**
* Set the template to be shown in the top bar
* @param tmpl template, null clears the content
*/
public setTopBarContent(tmpl: TemplateRef<any>) {
if (this.topBarContent) {
this.topBarContent.destroy();
}
if (tmpl) {
this.topBarContent = this.topBar.createEmbeddedView(tmpl);
}
}
}
The first issue I ran into was that the NavigationComponent
dependency was not available yet, when the TopBarDirective was initialized.我遇到的第一个问题是,当 TopBarDirective 被初始化时,
NavigationComponent
依赖项还不可用。 I got the following error:我收到以下错误:
ERROR Error: Uncaught (in promise): NullInjectorError:
错误错误:未捕获(承诺):NullInjectorError:
StaticInjectorError(AppModule)[TopBarDirective -> NavigationComponent]: StaticInjectorError(Platform: core)[TopBarDirective -> NavigationComponent]:
StaticInjectorError(AppModule)[TopBarDirective -> NavigationComponent]: StaticInjectorError(Platform: core)[TopBarDirective -> NavigationComponent]:
NullInjectorError: No provider for NavigationComponent!
NullInjectorError: 没有 NavigationComponent 的提供者!
So obviously the component got initialized after the directive and wasn't available yet.很明显,该组件在指令之后被初始化并且尚不可用。
I tried adding the NavigationComponent
to the providers
array of the AppComponent
and the dependency injection now worked:我尝试将
NavigationComponent
添加到AppComponent
的providers
数组中,现在依赖注入起作用了:
@NgModule({
declarations: [
NavigationComponent,
SomeComponent,
TopBarDirective
],
imports: [
BrowserModule,
CommonModule
],
providers: [NavigationComponent]
})
export class AppModule { }
However, it seems there are now two instances of the NavigationComponent.但是,现在似乎有两个 NavigationComponent 实例。 I checked this by generating a random number in the
constructor
of the NavigationComponent
and logging it.我通过在
NavigationComponent
的constructor
中生成一个随机数并记录它来检查这一点。 The directive definitely has an instance other from the one displayed at the <app-navigation>
selector.该指令肯定有一个不同于
<app-navigation>
选择器中显示的实例。
Now I know this pattern works somehow.现在我知道这种模式以某种方式起作用。 I found it some time ago where it was introduced by some Angular developer, but I unfortunately don't have the source anymore.
我前段时间发现它是由一些 Angular 开发人员引入的,但不幸的是我不再拥有源代码。 The working version, however, displays the contents in the
AppComponent
, so the directive only has a dependency to AppComponent
, which seems to get initialized first.然而,工作版本显示
AppComponent
的内容,因此该指令仅依赖于AppComponent
,它似乎首先被初始化。 Therefore the whole dependency issue does not occur.因此不会发生整个依赖问题。
How can I make sure the instance of NavigationComponent
provided to the TopBarDirective
is the same instance that is displayed at the <app-navigation>
selector?如何确保提供给
TopBarDirective
的NavigationComponent
实例与<app-navigation>
选择器中显示的实例相同?
I propose you to create a service say TopbarService
for this which would be like this.There we will use a BehaviorSubject
to set the template and emit it's latest value.我建议您为此创建一个服务,比如
TopbarService
,就像这样。我们将使用BehaviorSubject
来设置模板并发出它的最新值。
@Injectable()
export class TopbarService {
private currentState = new BehaviorSubject<TemplateRef<any> | null>(null);
readonly contents = this.currentState.asObservable();
setContents(ref: TemplateRef<any>): void {
this.currentState.next(ref);
}
clearContents(): void {
this.currentState.next(null);
}
}
Now in directive inject this service and invoke the service method.现在在指令中注入此服务并调用服务方法。
@Directive({
selector: '[appTopbar]',
})
export class TopbarDirective implements OnInit {
constructor(private topbarService: TopbarService,
private templateRef: TemplateRef<any>) {
}
ngOnInit(): void {
this.topbarService.setContents(this.templateRef);
}
}
In the NavigationComponent
component subscribe on the contents behaviorsubject to get the latest value and set the template.在
NavigationComponent
组件中订阅内容 behaviorsubject 以获取最新值并设置模板。
export class NavigationComponent implements OnInit, AfterViewInit {
_current: EmbeddedViewRef<any> | null = null;
@ViewChild('vcr', { read: ViewContainerRef })
vcr: ViewContainerRef;
constructor(private topbarService: TopbarService,
private cdRef: ChangeDetectorRef) {
}
ngOnInit() {
}
ngAfterViewInit() {
this.topbarService
.contents
.subscribe(ref => {
if (this._current !== null) {
this._current.destroy();
this._current = null;
}
if (!ref) {
return;
}
this._current = this.vcr.createEmbeddedView(ref);
this.cdRef.detectChanges();
});
}
}
Html of this component would be like this where you place the template.此组件的 Html 将是您放置模板的位置。
template: `
<div class="full-container topbar">
<ng-container #vcr></ng-container>
<h1>Navbar</h1>
</div>
`,
To inject a Controller into its Directive, use forwardRef .要将控制器注入其指令,请使用forwardRef 。
Component definition组件定义
@Component({
//...,
providers:[
{
provide: MyController,
useExisting: forwardRef(() => MyController)
}]
})
export class MyController {
//...
}
Directive definition指令定义
@Directive({
//...
})
export class MyDirective {
constructor(private ctlr: MyController) { }
}
That constructor might need an @Host();该构造函数可能需要一个@Host(); I haven't tested this code.
我还没有测试过这段代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.