Reputation: 20416
I made a Stackblitz for my code (link: https://stackblitz.com/edit/angular-iah7up ). This is my custom component that toggles two ng-content based on click in and clicks out.
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';
}
}
Template:
<div *ngIf="mode==='view'" >
<ng-content select="[view]"></ng-content>
</div>
<div *ngIf="mode==='edit'" >
<ng-content select="[edit]"></ng-content>
</div>
Usage:
<app-editable-inplace >
<div view>
<h3>Click to edit [not working :-( ]</h3>
</div>
<div edit>
<input placeholder="Click to edit" >
</div>
</app-editable-inplace>
But When I click on the view, clickOutside$ is triggered instantly (I don't know why)
And also, the line this.element.contains(target) === false
is not working because I can see in the console that the host contains the clicked item although always say that I clicked outside (I don't know why too)
Upvotes: 2
Views: 994
Reputation: 214047
In browser many events are bubbling(and click
event is one of them). It means that event goes from the target element straight up. And if you registered event with the same name somewhere above then it will be fired nevermind what happend in target handler.
document =====================================================> outsideHandler
body /\
.... ||
<app-editable-inplace> bubbling
<div view> /\
<h3>Click to edit [not working :-( ]</h3> <===== target || viewClickHandler
</div>
...
</app-editable-inplace>
So, as you have already guessed by this reason clickOutside$
is triggered instantly.
There are different ways you can use to fix it:
1) The most simple way is to use event.stopPropagation()
so that the event won't be bubbled further.
private viewModeHandler() {
fromEvent(this.element, 'click').pipe(
untilDestroyed(this)
).subscribe((e: any) => {
console.log('clicked inside');
e.stopPropagation(); <==================== add this
2) Since the bubbling event is the same in those handlers you can set some flag to prevent handling it in top handler.
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) Catch click event in capture phase:
fromEvent(this.element, 'click', { capture: true }).pipe(
...
const clickOutside$ = fromEvent(document, 'click', { capture: true })
Upvotes: 1
Reputation: 135
It is because you have bind your click event to entire document.
due to that your click outside counter increment.
all you have to do is to check click control event and then prevent propagation.
please review this article to understand more about events.
https://medium.com/@vsvaibhav2016/event-bubbling-and-event-capturing-in-javascript-6ff38bec30e
Upvotes: 0