Angular 将组件注入属性指令
Angular inject a component into an attribute directive
Tl;dr:如何提供可见组件作为指令的依赖项?自然地,组件必须在指令之前初始化,但它必须与应用程序稍后运行组件的 selector
时显示的实例相同。
详情:
我的 app.component.html 的结构如下:
app.component.html
<app-navigation></app-navigation>
<router-outlet></router-outlet>
顶部有一个始终可见的导航栏。 <router-outlet>
始终显示当前活动的组件。
我现在想要允许 <router-outlet>
中呈现的组件修改导航栏的内容,例如显示适合当前活动组件的附加按钮。这应该与指令一起使用,如下所示:
some.component.html
<div *appTopBar>
<button>Additional Button</button>
</div>
附加按钮现在应该出现在顶部的导航栏中。
appTopBar
指令如下所示:
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);
}
}
该指令依赖于 NavigationComponent,可以通过公开提供的方法将内容传递给导航栏 setTopBarContent()
:
navigation.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);
}
}
}
我 运行 遇到的第一个问题是 NavigationComponent
依赖项在初始化 TopBarDirective 时尚不可用。我收到以下错误:
ERROR Error: Uncaught (in promise): NullInjectorError:
StaticInjectorError(AppModule)[TopBarDirective -> NavigationComponent]:
StaticInjectorError(Platform: core)[TopBarDirective -> NavigationComponent]:
NullInjectorError: No provider for NavigationComponent!
很明显组件在指令之后被初始化并且还不可用。
我尝试将 NavigationComponent
添加到 AppComponent
的 providers
数组中,依赖注入现在起作用了:
@NgModule({
declarations: [
NavigationComponent,
SomeComponent,
TopBarDirective
],
imports: [
BrowserModule,
CommonModule
],
providers: [NavigationComponent]
})
export class AppModule { }
但是,现在似乎有两个 NavigationComponent 实例。我通过在 NavigationComponent
的 constructor
中生成一个 运行dom 编号并记录它来检查这一点。该指令肯定有一个与 <app-navigation>
选择器中显示的实例不同的实例。
现在我知道这种模式以某种方式起作用了。我在一段时间前发现它是由一些 Angular 开发人员介绍的,但不幸的是我没有源了。然而,工作版本显示 AppComponent
中的内容,因此该指令仅依赖于 AppComponent
,它似乎首先被初始化。因此不会发生整个依赖性问题。
如何确保提供给 TopBarDirective
的 NavigationComponent
实例与 <app-navigation>
选择器中显示的实例相同?
我建议你为此创建一个服务说 TopbarService
就像 this.There 我们将使用 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);
}
}
现在在指令中注入此服务并调用服务方法。
@Directive({
selector: '[appTopbar]',
})
export class TopbarDirective implements OnInit {
constructor(private topbarService: TopbarService,
private templateRef: TemplateRef<any>) {
}
ngOnInit(): void {
this.topbarService.setContents(this.templateRef);
}
}
在 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这个组件在你放置模板的地方是这样的。
template: `
<div class="full-container topbar">
<ng-container #vcr></ng-container>
<h1>Navbar</h1>
</div>
`,
要将控制器注入其指令,请使用 forwardRef。
组件定义
@Component({
//...,
providers:[
{
provide: MyController,
useExisting: forwardRef(() => MyController)
}]
})
export class MyController {
//...
}
指令定义
@Directive({
//...
})
export class MyDirective {
constructor(private ctlr: MyController) { }
}
该构造函数可能需要 @Host();我还没有测试过这段代码。
Tl;dr:如何提供可见组件作为指令的依赖项?自然地,组件必须在指令之前初始化,但它必须与应用程序稍后运行组件的 selector
时显示的实例相同。
详情:
我的 app.component.html 的结构如下:
app.component.html
<app-navigation></app-navigation>
<router-outlet></router-outlet>
顶部有一个始终可见的导航栏。 <router-outlet>
始终显示当前活动的组件。
我现在想要允许 <router-outlet>
中呈现的组件修改导航栏的内容,例如显示适合当前活动组件的附加按钮。这应该与指令一起使用,如下所示:
some.component.html
<div *appTopBar>
<button>Additional Button</button>
</div>
附加按钮现在应该出现在顶部的导航栏中。
appTopBar
指令如下所示:
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);
}
}
该指令依赖于 NavigationComponent,可以通过公开提供的方法将内容传递给导航栏 setTopBarContent()
:
navigation.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);
}
}
}
我 运行 遇到的第一个问题是 NavigationComponent
依赖项在初始化 TopBarDirective 时尚不可用。我收到以下错误:
ERROR Error: Uncaught (in promise): NullInjectorError:
StaticInjectorError(AppModule)[TopBarDirective -> NavigationComponent]: StaticInjectorError(Platform: core)[TopBarDirective -> NavigationComponent]:
NullInjectorError: No provider for NavigationComponent!
很明显组件在指令之后被初始化并且还不可用。
我尝试将 NavigationComponent
添加到 AppComponent
的 providers
数组中,依赖注入现在起作用了:
@NgModule({
declarations: [
NavigationComponent,
SomeComponent,
TopBarDirective
],
imports: [
BrowserModule,
CommonModule
],
providers: [NavigationComponent]
})
export class AppModule { }
但是,现在似乎有两个 NavigationComponent 实例。我通过在 NavigationComponent
的 constructor
中生成一个 运行dom 编号并记录它来检查这一点。该指令肯定有一个与 <app-navigation>
选择器中显示的实例不同的实例。
现在我知道这种模式以某种方式起作用了。我在一段时间前发现它是由一些 Angular 开发人员介绍的,但不幸的是我没有源了。然而,工作版本显示 AppComponent
中的内容,因此该指令仅依赖于 AppComponent
,它似乎首先被初始化。因此不会发生整个依赖性问题。
如何确保提供给 TopBarDirective
的 NavigationComponent
实例与 <app-navigation>
选择器中显示的实例相同?
我建议你为此创建一个服务说 TopbarService
就像 this.There 我们将使用 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);
}
}
现在在指令中注入此服务并调用服务方法。
@Directive({
selector: '[appTopbar]',
})
export class TopbarDirective implements OnInit {
constructor(private topbarService: TopbarService,
private templateRef: TemplateRef<any>) {
}
ngOnInit(): void {
this.topbarService.setContents(this.templateRef);
}
}
在 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这个组件在你放置模板的地方是这样的。
template: `
<div class="full-container topbar">
<ng-container #vcr></ng-container>
<h1>Navbar</h1>
</div>
`,
要将控制器注入其指令,请使用 forwardRef。
组件定义
@Component({
//...,
providers:[
{
provide: MyController,
useExisting: forwardRef(() => MyController)
}]
})
export class MyController {
//...
}
指令定义
@Directive({
//...
})
export class MyDirective {
constructor(private ctlr: MyController) { }
}
该构造函数可能需要 @Host();我还没有测试过这段代码。