为什么我无法访问此示例中另一个组件(不是子组件)中定义的 div 的 @ViewChild 引用?

Why I can't access to @ViewChild reference of a div defined in another component (not a subcomponent) in this example?

我在尝试使用 @ViewChild() 装饰器时发现一些问题。

我会尝试详细解释我要做什么。

我的项目有一个正常工作的版本(目前它只是我正在开发的一个最小原型),如果您可以在这里看到完整的代码:https://bitbucket.org/dgs_poste_team/soc_calendar/src/master/

所以基本上我有这个主 app-component.html 主视图:

<div class="container">
  <div class="row">
    <div class="col-12">
      <app-header></app-header>
    </div>

    <div class="row">
      <div class="col-4">
        <p>DRAGGABLE</p>
      </div>
      <div class="col-8">
        <app-fullcalendar></app-fullcalendar>
        </div>
      </div>
</div>

如您所见,它包含对 app-fullcalendar 组件的引用。该组件包含一个实现如下内容的日历:https://fullcalendar.io/docs/external-dragging-demo

哪里显示了一个日历和一个 "external events" 框,我可以在其中选择一个事件并将其拖到我的日历中。

在我的原始版本中它运行良好,但目前我通过以下简单的方式实现了它:

1) 这是我的完整日历。component.html 查看文件:

<p>fullcalendar works!</p>

<div class="content-section implementation">
  <p-fullCalendar #fullcalendar
                  [events]="events"
                  [options]="options">
  </p-fullCalendar>
</div>

<div #external>
  <p>
    <strong>Draggable Events</strong>
  </p>
  <app-people-list></app-people-list>
</div>

如您所见,它包含一个 div,其参考名称设置为 external,并且此 div 包含我的 [=73= 的子组件]app-fullcalendar 简单呈现事件列表的组件(app-people-list,在我的用例中,事件是我必须放在特定位置的人我日历上的日期,但这现在不那么重要了)。

然后进入处理 app-fullcalendar 组件行为的打字稿代码我有这个代码:

import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
import { EventService } from '../event.service';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list';
import interactionPlugin, { Draggable } from '@fullcalendar/interaction';
import { FullCalendar } from 'primeng';

@Component({
  selector: 'app-fullcalendar',
  templateUrl: './fullcalendar.component.html',
  styleUrls: ['./fullcalendar.component.css']
})
export class FullcalendarComponent implements OnInit {

  events: any[];
  options: any;
  header: any;

  //people: any[];

  @ViewChild('fullcalendar') fullcalendar: FullCalendar;
  @ViewChild('external') external: ElementRef;

  constructor(private eventService: EventService) {}

  ngOnInit() {
    this.eventService.getEvents().then(events => { this.events = events;});
    //this.eventService.getPeople().then(people => {this.people = people;});

    //console.log("PEOPLE: " + this.people);

    this.options = {
        plugins:[ dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin ],
        defaultDate: '2017-02-01',
        header: {
            left: 'prev,next',
            center: 'title',
            right: 'dayGridMonth,timeGridWeek,timeGridDay'
        },
        editable: true,

        dateClick: (dateClickEvent) =>  {         // <-- add the callback here as one of the properties of `options`
          console.log("DATE CLICKED !!!");
        },

        eventClick: (eventClickEvent) => {
          console.log("EVENT CLICKED !!!");
        },

        eventDragStop: (eventDragStopEvent) => {
          console.log("EVENT DRAG STOP !!!");
        },

        eventReceive: (eventReceiveEvent) => {
          console.log("EXTERNAL EVENT LAND ON THE CALENDAR");
          //console.log(eventReceiveEvent);
          this.eventService.addEvent(eventReceiveEvent);
        }
    };

  }

  ngAfterViewInit() {
    //console.log("ngAfterViewInit() START !!!")

    new Draggable(this.external.nativeElement, {
      itemSelector: '.fc-event',
      eventData: function(eventEl) {
        console.log("DRAG !!!");
        return {
          title: eventEl.innerText
        };
      }
    });

  }

}

所以在这段代码中,我引用了 div 的引用,引用名称为 external,通过这一行:

@ViewChild('external') external: ElementRef;

所以这个外部属性包含这个特定 div 的引用,它将包含我必须拖到我的日历中的 events\people 列表。

然后我在 ngAfterViewInit() 中使用此引用 属性 以允许拖放 div 中显示的具有引用 外部.

ngAfterViewInit() { //console.log("ngAfterViewInit() START !!!")

new Draggable(this.external.nativeElement, {
  itemSelector: '.fc-event',
  eventData: function(eventEl) {
    console.log("DRAG !!!");
    return {
      title: eventEl.innerText
    };
  }

注意:在项目选择器中指定的 .fc-class 内呈现,并放在event\person 列表中的每个元素都已呈现。

完美它工作正常!!!

现在我的问题是:我的想法是这样代码耦合太强,我想以这种方式解耦它:

1) div 引用了我的 #external 并包含我的 呈现 people\event 的列表必须拖入我的日历不再是我的 full-calendar 组件的子组件,但它在我的 app-component[=99= 中处于同一级别].

所以基本上我将它从我的 fullcalendar 中删除。component.html 变成:

<p>fullcalendar works!</p>

<div class="content-section implementation">
  <p-fullCalendar #fullcalendar
                  [events]="events"
                  [options]="options">
  </p-fullCalendar>
</div>

<!-- REMOVED -->
<!--
<div #external>
  <p>
    <strong>Draggable Events</strong>
  </p>

  <app-people-list></app-people-list>
</div>
-->

我把它放在 app.component.html 文件的左栏中,现在是:

好的,现在 people\event 的列表呈现在左侧,但我不能再将 event\person 从该列表拖到我的日历中。这是因为在 JavaScript 控制台中我收到此错误消息:

core.js:6272 ERROR TypeError: Cannot read property 'nativeElement' of undefined
    at FullcalendarComponent.ngAfterViewInit (fullcalendar.component.ts:67)
    at callHook (core.js:4770)
    at callHooks (core.js:4734)
    at executeInitAndCheckHooks (core.js:4674)
    at refreshView (core.js:12060)
    at refreshComponent (core.js:13461)
    at refreshChildComponents (core.js:11701)
    at refreshView (core.js:12036)
    at renderComponentOrTemplate (core.js:12114)
    at tickRootContext (core.js:13668)

此错误发生在 fullcalendar.app.component.ts 文件的 ngAfterViewInit() 函数中:

ngAfterViewInit() { //console.log("ngAfterViewInit() START !!!")

new Draggable(this.external.nativeElement, {
  itemSelector: '.fc-event',
  eventData: function(eventEl) {
    console.log("DRAG !!!");
    return {
      title: eventEl.innerText
    };
  }
});

执行此挂钩时,此组件似乎无法再通过 #external 引用检索 div。 我怀疑发生这种情况是因为在 Angular 完全呈现 fullcalendar.app.component 组件后自动执行此挂钩函数。

在第一个工作示例中它起作用了,因为 app-people-list 是一个子组件,所以这个函数执行发生在所有子组件被渲染之后。但是现在我的 app-people-list 不再是一个子组件,我怀疑这个 ngAfterViewInit() 不再是正确的解决方案。这个想法对吗?

我该怎么做才能解决这个问题并使我的示例正常工作?我需要使用另一个钩子还是有其他好的解决方案?

我想你可以使用内容投影来解决这个问题。

所以,如果我理解正确的话,现在你有这样的东西:

app.component.html

<!-- ... -->
<app-header></app-header>

<app-fullcalendar></app-fullcalendar>

<div #external>
  <!-- ... -->
</div>

如果是这样的话,我想你可以这样做:

app.component.html

<app-fullcalendar>
  <div #external>
    <!-- ... -->
  </div>
</app-fullcalendar>

app-fullcalendar.component.ts

@ContentChild('external') external: ElementRef<HTMLDivElement>;

ngAfterContentInit () {
  /* this.external... */
}

编辑

我认为这可行。

app.component.html

<!-- Left column -->
<!-- <app-test2 #external></app-test2> -->

<div #external>
  test23112
</div>

<!-- Right column -->
<app-test1>
  <ng-container [share-comp]="external"></ng-container>
</app-test1>

分享-comp.directive.ts


@Directive({
  selector: '[share-comp]'
})
export class ShareCompDirective {

  @Input('share-comp') shared: any

  constructor() { }

}

应用-test1.component.ts

(app.calendar 在你的情况下)

  @ContentChild(ShareCompDirective) private external: ShareCompDirective;

  constructor() { }

  ngOnInit() {
  }

  ngAfterContentInit () {
    // If the `external` part is a component
    // const nativeElem = (this.external.shared.elem as ElementRef).nativeElement;

    // If the shared part is a DOM element
    const nativeElem = this.external.shared
    console.log(nativeElem)
  }

ng-run demo

ViewChild 仅适用于该组件模板中声明的内容,而不适用于整个应用程序模板。

但您可以将模板引用作为输入传递,没问题...

<app-fullcalendar [externals]="externals"></app-fullcalendar>

并且您可以将 externals 声明为完整日历中的输入而不是视图子项:

@Input() externals: ElementRef

您可以在 ngOnInit 或更高版本的挂钩中使用您认为合适的。

但是,如果我没看错的话,这似乎是不必要的,您需要做的就是移动这部分:

  @ViewChild('external') external: ElementRef;

  ngAfterViewInit() {
    //console.log("ngAfterViewInit() START !!!")

    new Draggable(this.external.nativeElement, {
      itemSelector: '.fc-event',
      eventData: function(eventEl) {
        console.log("DRAG !!!");
        return {
          title: eventEl.innerText
        };
      }
    });

  }

fullcalendar.component.ts 出来,进入 app.component.ts 控制器。由于您对子视图所做的所有操作都是将其声明为可拖动,因此这在应用程序组件中应该可以正常工作,并且您实际上不需要在完整日历中访问它。