简体   繁体   English

Angular 将组件注入到属性指令中

[英]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添加到AppComponentproviders数组中,现在依赖注入起作用了:

@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.我通过在NavigationComponentconstructor中生成一个随机数并记录它来检查这一点。 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?如何确保提供给TopBarDirectiveNavigationComponent实例与<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.

相关问题 如何在属性指令中注入指令或组件? - How to inject a directive or component within an attribute directive? 在Angular 4中对组件应用属性指令 - Apply attribute directive on component in Angular 4 Angular2:通过指令将外部Component注入其他组件无效 - Angular2: Inject external Component into other component via directive not working 在 Angular 中,有没有办法将包含组件指令的 html 注入到父组件的模板中? - In Angular is there a way to inject html that contains a component directive into the template of a parent component? Angular2-自定义输入组件的属性指令 - Angular2 - Attribute directive for a custom input component 在Angular 2中从子组件更改指令属性 - Change directive attribute from child component in Angular 2 Angular2如何将父组件注入指令只有它在那里? - Angular2 how to inject parent component into directive only if it's there? 将具有自定义angular指令和属性的HTML元素添加到Angular Component中 - Add HTML elements with custom angular directive and attribute into Angular Component Angular - 使用Render2在Angular组件中动态设置属性指令 - Angular - Setting an Attribute Directive dynamically in an Angular component using Render2 如何将子类的指令注入组件 - How to inject directive of subclass into component
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM