我为自己的代码创建了Stackblitz(链接:https://stackblitz.com/edit/angular-iah7up)。这是我的自定义组件,它基于单击和单击来切换两个ng内容。
import {Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output} from '@angular/core';
import {fromEvent, Subject} from "rxjs";
import {untilDestroyed} from "ngx-take-until-destroy";
import {filter, switchMapTo, take} from "rxjs/operators";
@Component({
selector: 'app-editable-inplace',
templateUrl: './editable-inplace.component.html',
styleUrls: ['./editable-inplace.component.css']
})
export class EditableInplaceComponent implements OnInit, OnDestroy {
@Output() update = new EventEmitter();
mode: 'view' | 'edit' = 'view';
editMode = new Subject();
editMode$ = this.editMode.asObservable();
ngOnInit(): void {
this.viewModeHandler();
this.editModeHandler();
}
constructor(private host: ElementRef) {
}
// This method must be present, even if empty.
ngOnDestroy() {
// To protect you, we'll throw an error if it doesn't exist.
}
private viewModeHandler() {
fromEvent(this.element, 'click').pipe(
untilDestroyed(this)
).subscribe(() => {
console.log('clicked inside')
this.editMode.next(true);
this.mode = 'edit';
});
}
private editModeHandler() {
const clickOutside$ = fromEvent(document, 'click').pipe(
filter(({target}) => {
console.log(this.mode)
console.log('parent', this.element, 'child', target)
const ans = this.element.contains(target) === false
console.log('clickoutside', ans)
return ans
}),
take(1)
)
this.editMode$.pipe(
switchMapTo(clickOutside$),
untilDestroyed(this)
).subscribe(event => {
this.update.next();
this.mode = 'view';
});
}
get element() {
return this.host.nativeElement;
}
toViewMode() {
this.update.next();
this.mode = 'view';
}
}
模板:
<div *ngIf="mode==='view'" >
<ng-content select="[view]"></ng-content>
</div>
<div *ngIf="mode==='edit'" >
<ng-content select="[edit]"></ng-content>
</div>
用法:
<app-editable-inplace >
<div view>
<h3>Click to edit [not working :-( ]</h3>
</div>
<div edit>
<input placeholder="Click to edit" >
</div>
</app-editable-inplace>
但是当我单击视图时,clickOutside $会立即触发(我不知道为什么)
而且,this.element.contains(target) === false
行不起作用,因为尽管总是说我在外部单击(我也不知道为什么),但我在控制台中看到主机包含被单击的项目
答案 0 :(得分:0)
这是因为您已将click事件绑定到整个文档。
由于您的外部点击计数器增加了。
您要做的就是检查点击控制事件,然后阻止传播。
请阅读本文以了解有关事件的更多信息。
https://medium.com/@vsvaibhav2016/event-bubbling-and-event-capturing-in-javascript-6ff38bec30e
答案 1 :(得分:0)
在浏览器中,许多事件是bubbling(而click
事件是其中之一)。这意味着事件直接从目标元素开始。而且,如果您在上方某处注册了相同名称的事件,那么它将被解雇,不要介意目标处理程序中发生的情况。
document =====================================================> outsideHandler
body /\
.... ||
<app-editable-inplace> bubbling
<div view> /\
<h3>Click to edit [not working :-( ]</h3> <===== target || viewClickHandler
</div>
...
</app-editable-inplace>
因此,正如您已经猜到的那样,clickOutside$
会立即触发。
您可以使用多种方法来修复它:
1)最简单的方法是使用event.stopPropagation()
,这样事件就不会再冒泡了。
private viewModeHandler() {
fromEvent(this.element, 'click').pipe(
untilDestroyed(this)
).subscribe((e: any) => {
console.log('clicked inside');
e.stopPropagation(); <==================== add this
2)由于这些处理程序中的冒泡事件相同,因此可以设置一些标志以防止在顶级处理程序中对其进行处理。
private viewModeHandler() {
fromEvent(this.element, 'click').pipe(
untilDestroyed(this)
).subscribe((e: any) => {
console.log('clicked inside');
e.fromInside = true;
...
const clickOutside$ = fromEvent(document, 'click').pipe(
filter((e: any) => {
return !e.fromInside && this.element.contains(e.target) === false
}),
3)在捕获阶段捕获点击事件:
fromEvent(this.element, 'click', { capture: true }).pipe(
...
const clickOutside$ = fromEvent(document, 'click', { capture: true })