为什么我无法访问此示例中另一个组件(不是子组件)中定义的 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)
}
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
控制器。由于您对子视图所做的所有操作都是将其声明为可拖动,因此这在应用程序组件中应该可以正常工作,并且您实际上不需要在完整日历中访问它。
我在尝试使用 @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)
}
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
控制器。由于您对子视图所做的所有操作都是将其声明为可拖动,因此这在应用程序组件中应该可以正常工作,并且您实际上不需要在完整日历中访问它。