ngIf not working - html

I'm using ngIf in a parent template. There I have the following code.
In html:
<button *ngIf="saveButton" type="button" class="button-success" (click)="save()">Save</button>
<add (setSavebutton)="setSavebutton($event)"></add>
In the component:
private setSavebutton(_boolean: any) {
this.saveButton = _boolean;
console.log(this.saveButton); // true
}
private save() { // save my item set by child }
In my child (add) I do:
#Output() setSavebutton: EventEmitter<any> = new EventEmitter<any>();
if (this.form.valid) {
console.log(this.form.valid); // true
this.setSavebutton.emit(true);
}
Both console.log return true but my button is not showing.
What am I doing wrong here?
EDIT
I found a work around.. They code of the child is the same.
In parent html:
<button [hidden]="!saveButton" type="button" class="button-success" (click)="save()">Save</button>
Why does the hidden work and the ngIf not?

It's not clear from your question where you code is called from.
A general workaround:
constructor(private cdRef:ChangeDetectorRef){}
private setSavebutton(_boolean: any) {
this.saveButton = _boolean;
this.cdRef.detectChanges();
console.log(this.saveButton); // true
}
With more information, it could be possible to suggest how to fix the root cause.

Related

Angular: ExpressionChangedAfterItHasBeenCheckedError when trying to disable button

I use mat-dialog to edit details of my profile page. I'm getting an ExpressionChangedAfterItHasBeenCheckedError when I click the 'Edit age' button and the dialog window pops up.
I decided to extract the styling of all edit dialogs into a single edit.component:
edit.component.html
<div class="navigation-control">
<mat-icon (click)="onCancelButtonClicked()"
class="close-button">close</mat-icon>
</div>
<div class="content-main">
<ng-content select=".content-main"></ng-content>
</div>
<div class="content-bot">
<button mat-raised-button
(click)="onCancelButtonClicked()">Cancel</button>
<button mat-raised-button
(click)="onActionButtonClicked()"
[lnDisableButton]="actionButtonDisabled">{{actionButtonValue}}</button>
</div>
edit.component.ts
#Component({ selector: 'ln-edit', ... })
export class EditComponent {
#Input() actionButtonValue: string;
#Input() actionButtonDisabled: boolean;
#Output() cancelButtonClicked = new EventEmitter<void>();
#Output() actionButtonClicked = new EventEmitter<void>();
onCancelButtonClicked() {
this.cancelButtonClicked.emit();
}
onActionButtonClicked() {
this.actionButtonClicked.emit();
}
}
To avoid the ExpressionChangedAfterItHasBeenCheckedError when trying to disable buttons and controls, I used this snippet. But that didn't solve this issue.
disable-button.directive.ts
#Directive({ selector: '[lnDisableButton]' })
export class DisableButtonDirective {
#Input('lnDisableButton') isDisabled = false;
#HostBinding('attr.disabled')
get disabled() { return this.isDisabled; }
}
The following is the contents of a mat-dialog window. This gets instantiated when I click the 'Edit age' button. When I remove the [actionButtonDisabled]="actionButtonDisabled", the error goes away, but obivously I need that line to make the functionality disable the button.
age-edit.component.html
<ln-edit [actionButtonValue]="actionButtonValue"
[actionButtonDisabled]="actionButtonDisabled"
(cancelButtonClicked)="onCancelButtonClicked()"
(actionButtonClicked)="onActionButtonClicked()">
<form [formGroup]="ageForm"
class="content-main">
<ln-datepicker formControlName="birthday"
[appearance]="'standard'"
[label]="'Birthday'"
class="form-field">
</ln-datepicker>
</form>
</ln-edit>
I handle the disabling/enabling the button in the 'ts' part of the mat-dialog popup.
age-edit.component.ts
#Component({ selector: 'ln-age-edit', ... })
export class AgeEditComponent implements OnInit, OnDestroy {
ageForm: FormGroup;
private initialFormValue: any;
actionButtonDisabled = true;
private unsubscribe = new Subject<void>();
constructor(
private editPhotoDialogRef: MatDialogRef<AgeEditComponent>,
private fb: FormBuilder,
#Inject(MAT_DIALOG_DATA) public dialogData: Date) { }
ngOnInit() {
this.initializeAgeForm();
this.loadDataToAgeForm(this.dialogData);
this.trackFormDistinct();
}
private initializeAgeForm(): void {
this.ageForm = this.fb.group({
birthday: null,
});
}
loadDataToAgeForm(birthday: Date | null): void {
if (!birthday) { return; }
this.ageForm.setValue({ birthday });
this.initialFormValue = this.ageForm.value;
}
get birthdayAC() { return this.ageForm.get('birthday') as AbstractControl; }
get actionButtonValue(): string {
return this.birthdayAC.value ? 'Update age' : 'Add age';
}
onCancelButtonClicked(): void {
this.editPhotoDialogRef.close();
}
onActionButtonClicked(): void {
this.editPhotoDialogRef.close({ ... });
}
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
ngOnDestroy() { ... }
}
I suspect this has something to do with content projection, but I'm not sure.
(...or perhaps with my custom 'ln-datepicker'?)
Any ideas?
Thanks.
From what I can tell, the problem resides in trackFormDistinct() method:
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
Looks like because of this.ageForm.valueChanges, will have different values in the 2 change detection cycles. I think this.ageForm.valueChanges emits due to <ln-datepicker>.
In a tree of form controls, if one node calls setValue, all its ancestors will have to be updated. I've written more about how Angular Forms work in this article.
I'm thinking of 2 alternatives:
skip the first emission of ageForm since it indicates the initialization of the form control tree, so this is irrelevant to the logic inside subscribe's callback.
this.ageForm.valueChanges.pipe(
skip(1),
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(/* .... */)
initialize actionButtonDisabled with false, since the error complains that it switched from true to false
actionButtonDisabled = false;

Cant create dynamic textboxes using Renderer2 in angular4

i'm working on Angular 4 and creating textboxes dynamically by clicking on a button using Renderer2. I had tried it on a dummy project first and it works well but when i put the code in my real project it won't work and there is no error on console and i had checked that the function is triggering or not by putting console.log in it and function is triggering and message is showing in the console but textbox creating code is not working. Can anyone help me in this?
Type script function
constructor(private renderer:Renderer2, private el: ElementRef ) { }
addfield() {
console.log('function triggered');
const div = this.renderer.createElement('div');
const input = this.renderer.createElement('input');
this.renderer.appendChild(div, input);
this.renderer.addClass(div, 'col-md-6');
this.renderer.addClass(div, 'col-sm-6');
this.renderer.addClass(div, 'col-xs-12');
console.log('cross passes the code');
this.renderer.addClass(input, 'form-control');
this.renderer.addClass(input, 'col-md-7');
this.renderer.addClass(input, 'col-xs-12');
}
Html code
<button class="btn btn-success" (click)="addfield()" >Add New Fiels +</button>
<div id="textboxes"></div>
<button class="btn btn-success" (click)="addfield()" >Add New Fiels +</button>
//
constructor(private renderer: Renderer2) {
}
addfield() {
....
const textboxes = document.querySelector('#textboxes'); //to get element
this.renderer.addClass(textboxes , "col-md-6"); //this is for add class
let divel= this.renderer.createElement('div'); //this is for create an element
this.renderer.appendChild(textboxes, divel); //this is for append a child element
}
Your code is absolutely fine, only issue is you are not appending dynamically created
elements to the ui
Component :
constructor(private renderer:Renderer2, private el: ElementRef ) {}
addfield() {
....
const textboxes = document.getElementById('textboxes');
this.renderer.appendChild(textboxes, div);
}
Template :
<div id="textboxes"></div>
<button class="btn btn-success" (click)="addfield()" >Add New Fiels +</button>
Here is the link to working demo :
https://stackblitz.com/edit/angular-dynamic-textbox
Try like this :
missing the following line to add dynamic textboxes this.renderer.appendChild(this.el.nativeElement, div);
addfield() {
const div = this.renderer.createElement('div');
const input = this.renderer.createElement('input');
this.renderer.appendChild(div, input);
this.renderer.addClass(div, 'col-md-6');
this.renderer.addClass(div, 'col-sm-6');
this.renderer.addClass(div, 'col-xs-12');
this.renderer.addClass(input, 'form-control');
this.renderer.addClass(input, 'col-md-7');
this.renderer.addClass(input, 'col-xs-12');
this.renderer.appendChild(this.el.nativeElement, div);
}

How to make Angular 2 render HTML template after a promise in the component is resolved?

For my app, the ItemDetailComponent is where info of an item will be displayed. I have a service that retrieves all items using promise. I use ActivatedRoute to retrieve the item ID from the url path, then run the service, get all items, then find the item with the ID retrieved above, and assign it to selectedItem variable.
Here is item-detail.component.ts:
export class ItemDetailComponent implements OnInit {
private title = 'Item Details'
private selectedItem: object
constructor(
private route: ActivatedRoute,
private itemService: ItemService
) {}
ngOnInit() {
const selectedItemId = this.route.snapshot.params.itemId
return this.itemService.getAllItems()
.then((items) => {
return _.find(items, item => item.itemId === selectedItemId)
})
.then((selectedItem) => {
this.selectedItem = selectedItem
console.log('Inside promise', this.selectedItem)
})
console.log('Outside promise', this.selectedItem)
}
}
And here is item-detail.component.html template so I could display my item, just an example:
<div>
<h1>{{title}}</h1>
<div *ngIf="selectedItem">
<div><label>Item ID: </label>{{selectedItem.itemId}}</div>
</div>
</div>
The app returns nothing but the title unfortunately. I then added the two console.log() commands and found out that the one outside of the promise as well as the html template are rendered before the promise is fulfilled, and no selectedItem is available at that time. How could I force the app to execute them only after the promise is resolved in order to have the selectedItem in place for displayed?
EDIT: I added a new line in the html template to examine further:
<div>
<h1>{{title}}</h1>
<div><label>Item ID 1: </label>{{selectedItem.itemId}}</div>
<div *ngIf="selectedItem">
<div><label>Item ID 2: </label>{{selectedItem.itemId}}</div>
</div>
</div>
The app displays "Item ID 1:" label but with no actual id there. The console shows me an error saying that "Cannot read property 'itemId' of undefined", again confirming that the whole template is rendered before promise resolved and is not re-rendered after data is loaded. So weird.
You could create a Resolver for the route that fetches the desired data.
https://angular.io/api/router/Resolve
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
Add a boolean variable in to your class like
private dataAvailable:boolean=false;
and in the subscription to the promise,make this true when the data is available
then((selectedItem) => {
this.selectedItem = selectedItem;
this.dataAvailable=true;
console.log('Inside promise', this.selectedItem)
})
and in the template render when the data is available
<div>
<h1>{{title}}</h1>
<div *ngIf="dataAvailable">
<div><label>Item ID: </label>{{selectedItem.itemId}}</div>
</div>
</div>
It should do the trick
Update
ngOnInit() seems to be just a event handler hook - returning anything won't affect anything it seems. Hence my old answer will not work.
There are other workarounds like using *ngIf or putting it in routes etc. but I wish there was something like resolvePromise(): Promise hook that would put a condition on resolution before rendering.
This is instead of developers putting the boilerplate in every component.
Old answer
Most likely that is because you are missing return statement in the second then.
then((selectedItem) => {
this.selectedItem = selectedItem
console.log():
return selectedItem;//
}
Is it possible that the ChangeDetection is set to OnPush somewhere up the component tree?
If that is the case, the template does not automatically rerender, because nothing triggers the ChangeDetection for this component.
Look out for a Component with the setting changeDetection: ChangeDetectionStrategy.OnPush
#Component({
selector: 'example',
template: `...`,
styles: [`...`],
changeDetection: ChangeDetectionStrategy.OnPush
})
Also you already have a valid solution by using a Resolver you could check if this helps:
export class ItemDetailComponent implements OnInit {
private title = 'Item Details'
private selectedItem: object
constructor(
private route: ActivatedRoute,
private itemService: ItemService,
// the reference to the components changeDetector is needed.
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
const selectedItemId = this.route.snapshot.params.itemId
return this.itemService.getAllItems()
.then((items) => {
return _.find(items, item => item.itemId === selectedItemId)
})
.then((selectedItem) => {
this.selectedItem = selectedItem
// this triggers the changedetection and the template should be rerendered;
this.changeDetectorRef.detectChanges();
console.log('Inside promise', this.selectedItem)
});
console.log('Outside promise', this.selectedItem)
}
}
Here is a great article about Angulars ChangeDetection: https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

Angular 2 contenteditable div, move caret to end position

I have following Angular 2 code:
write.component.ts:
import { CommentHeaderComponent } from './comment.header.component';
import { StyleService } from './../../services/services/style.service';
import { Parameters } from './../../services/services/parameters';
import { Component, Input, ViewChild,ElementRef,HostBinding} from "#angular/core";
import { ContenteditableModel } from './../directives/contenteditable.directive';
#Component({
selector:'write',
templateUrl:'app/templates/write.component.html',
styleUrls:['app/templates/css/write.component.css']
})
export class WriteComponent
{
#HostBinding('class.hbox')
parameters:Parameters;
private writeText:string="";
private rows:number=1;
private maxRows:number=4;
private comment:boolean=false;
private lineHeight:string="1.7em";
#ViewChild('writeBox') writeBox:ElementRef;
constructor(private stService:StyleService){}
ngOnInit()
{
this.writeBox.nativeElement.innerText="";
this.stService.setProperty(this.writeBox,[{rows:this.rows}]);
this.stService.setStyle(this.writeBox,[{lineHeight:this.lineHeight}]);
if (this.parameters.Iskey("comment"))
{
this.comment=true;
}
}
write(data:any)
{
this.parameters.cfunc({event:"comment",message : this.writeText});
this.writeText="";
}
getWriteText():String
{
return this.writeText;
}
}
write.component.html:
<div contenteditable="true" #writeBox [ceModel]="writeText" (ceChange)="writeText=$event" [innerHTML]="writeText">
</div>
<ng-template [ngIf]="this.comment">
<button type="button" (click)="files($event)" class="btn fa fa-paperclip"> </button>
</ng-template>
<button type="button" (click)="write($event)" class="btn fa fa-chevron-right"> </button>
contenteditable.directive.ts:
import { ValidatorService } from './../../services/service/validator.service';
import { Directive, HostListener, Input, ElementRef, OnInit, SimpleChanges, EventEmitter, Output } from '#angular/core';
#Directive({selector: '[ceModel]'})
export class ContenteditableModel
{
#Input('ceModel') ceModel: any="";
#Output('ceChange') ceChange = new EventEmitter();
constructor(private elRef: ElementRef,private v:ValidatorService) {}
#HostListener('keyup', ['$event'])
onChange($event: any)
{
if ($event.keyCode==32)
{
console.log(this.ceModel);
this.ceModel="<a>"+this.ceModel+"<\a>";
this.ceChange.emit(this.ceModel);
return;
}
this.ceModel=this.elRef.nativeElement.innerText;
this.ceChange.emit(this.ceModel);
}
#HostListener('paste', ['$event'])
onPaste($event: any)
{
console.log($event);
}
}
I want to update dynamically a contenteditable div, see write.component.html, that is linked to a model that is managed by a directive,contenteditable.directive.ts. The class variable writeText is sent to the directive in order to check whether a user has written a url, if this is the case, the url content should be transformed to: url. This is rendered in the div as html:
The problem is, that whenever I type some text, the caret/cursor jumps always
to the start position:
Is it possible to manually move the cursor to end of the div content?
The content may be text and html.
I have tried the solutions proposed here with no luck:
Set the caret position always to end in contenteditable div
Thank you for your help.
in my problem, I trigger focus when enter (it auto go to new line) and refocus (to the last character of last todo) if i delete
....
// somewhere you trigger that focus event:
(div:HTMLElement) => {
// div.focus();
if (div.lastChild) this.setSelectionRange(div)
else div.focus();
this.changeDetector.detectChanges();
}
....
setSelectionRange(el:HTMLElement) {
let range = document.createRange();
let pos = el.lastChild.textContent.length;
let sel = window.getSelection();
console.log('el last child',el.lastChild.textContent.length,typeof(el.lastChild.textContent.length));
range.setStart(el.lastChild, pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
notice at "el.lastChild", your contenteditable using (textContent or innerHTML?)
I hope you're still interested in getting an answer to this. The easiest approach I can imagine is to save a reference to the <a> node and use it to place the caret.
Inside ContenteditableModel you simply can create the following:
private moveCaret(): void {
let range = document.createRange(),
pos = this.elRef.lastChild.innerText.length - 1,
sel = window.getSelection();
range.setStart(this.elRef, pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
This allows you to use your elRef to place the caret at the last char of its last child, the <a> element.

Detect click outside Angular component

How can I detect clicks outside a component in Angular?
import { Component, ElementRef, HostListener, Input } from '#angular/core';
#Component({
selector: 'selector',
template: `
<div>
{{text}}
</div>
`
})
export class AnotherComponent {
public text: String;
#HostListener('document:click', ['$event'])
clickout(event) {
if(this.eRef.nativeElement.contains(event.target)) {
this.text = "clicked inside";
} else {
this.text = "clicked outside";
}
}
constructor(private eRef: ElementRef) {
this.text = 'no clicks yet';
}
}
A working example - click here
An alternative to AMagyar's answer. This version works when you click on element that gets removed from the DOM with an ngIf.
http://plnkr.co/edit/4mrn4GjM95uvSbQtxrAS?p=preview
private wasInside = false;
#HostListener('click')
clickInside() {
this.text = "clicked inside";
this.wasInside = true;
}
#HostListener('document:click')
clickout() {
if (!this.wasInside) {
this.text = "clicked outside";
}
this.wasInside = false;
}
Binding to a document click through #Hostlistener is costly. It can and will have a visible performance impact if you overuse it (for example, when building a custom dropdown component and you have multiple instances created in a form).
I suggest adding a #Hostlistener() to the document click event only once inside your main app component. The event should push the value of the clicked target element inside a public subject stored in a global utility service.
#Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(private utilitiesService: UtilitiesService) {}
#HostListener('document:click', ['$event'])
documentClick(event: any): void {
this.utilitiesService.documentClickedTarget.next(event.target)
}
}
#Injectable({ providedIn: 'root' })
export class UtilitiesService {
documentClickedTarget: Subject<HTMLElement> = new Subject<HTMLElement>()
}
Whoever is interested for the clicked target element should subscribe to the public subject of our utilities service and unsubscribe when the component is destroyed.
export class AnotherComponent implements OnInit {
#ViewChild('somePopup', { read: ElementRef, static: false }) somePopup: ElementRef
constructor(private utilitiesService: UtilitiesService) { }
ngOnInit() {
this.utilitiesService.documentClickedTarget
.subscribe(target => this.documentClickListener(target))
}
documentClickListener(target: any): void {
if (this.somePopup.nativeElement.contains(target))
// Clicked inside
else
// Clicked outside
}
Improving J. Frankenstein's answer:
#HostListener('click')
clickInside($event) {
this.text = "clicked inside";
$event.stopPropagation();
}
#HostListener('document:click')
clickOutside() {
this.text = "clicked outside";
}
The previous answers are correct, but what if you are doing a heavy process after losing the focus from the relevant component? For that, I came with a solution with two flags where the focus out event process will only take place when losing the focus from relevant component only.
isFocusInsideComponent = false;
isComponentClicked = false;
#HostListener('click')
clickInside() {
this.isFocusInsideComponent = true;
this.isComponentClicked = true;
}
#HostListener('document:click')
clickout() {
if (!this.isFocusInsideComponent && this.isComponentClicked) {
// Do the heavy processing
this.isComponentClicked = false;
}
this.isFocusInsideComponent = false;
}
ginalx's answer should be set as the default one imo: this method allows for many optimizations.
The problem
Say that we have a list of items and on every item we want to include a menu that needs to be toggled. We include a toggle on a button that listens for a click event on itself (click)="toggle()", but we also want to toggle the menu whenever the user clicks outside of it. If the list of items grows and we attach a #HostListener('document:click') on every menu, then every menu loaded within the item will start listening for the click on the entire document, even when the menu is toggled off. Besides the obvious performance issues, this is unnecessary.
You can, for example, subscribe whenever the popup gets toggled via a click and start listening for "outside clicks" only then.
isActive: boolean = false;
// to prevent memory leaks and improve efficiency, the menu
// gets loaded only when the toggle gets clicked
private _toggleMenuSubject$: BehaviorSubject<boolean>;
private _toggleMenu$: Observable<boolean>;
private _toggleMenuSub: Subscription;
private _clickSub: Subscription = null;
constructor(
...
private _utilitiesService: UtilitiesService,
private _elementRef: ElementRef,
){
...
this._toggleMenuSubject$ = new BehaviorSubject(false);
this._toggleMenu$ = this._toggleMenuSubject$.asObservable();
}
ngOnInit() {
this._toggleMenuSub = this._toggleMenu$.pipe(
tap(isActive => {
logger.debug('Label Menu is active', isActive)
this.isActive = isActive;
// subscribe to the click event only if the menu is Active
// otherwise unsubscribe and save memory
if(isActive === true){
this._clickSub = this._utilitiesService.documentClickedTarget
.subscribe(target => this._documentClickListener(target));
}else if(isActive === false && this._clickSub !== null){
this._clickSub.unsubscribe();
}
}),
// other observable logic
...
).subscribe();
}
toggle() {
this._toggleMenuSubject$.next(!this.isActive);
}
private _documentClickListener(targetElement: HTMLElement): void {
const clickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
this._toggleMenuSubject$.next(false);
}
}
ngOnDestroy(){
this._toggleMenuSub.unsubscribe();
}
And, in *.component.html:
<button (click)="toggle()">Toggle the menu</button>
Alternative to MVP, you only need to watch for Event
#HostListener('focusout', ['$event'])
protected onFocusOut(event: FocusEvent): void {
console.log(
'click away from component? :',
event.currentTarget && event.relatedTarget
);
}
Solution
Get all parents
var paths = event['path'] as Array<any>;
Checks if any parent is the component
var inComponent = false;
paths.forEach(path => {
if (path.tagName != undefined) {
var tagName = path.tagName.toString().toLowerCase();
if (tagName == 'app-component')
inComponent = true;
}
});
If you have the component as parent then click inside the component
if (inComponent) {
console.log('clicked inside');
}else{
console.log('clicked outside');
}
Complete method
#HostListener('document:click', ['$event'])
clickout(event: PointerEvent) {
var paths = event['path'] as Array<any>;
var inComponent = false;
paths.forEach(path => {
if (path.tagName != undefined) {
var tagName = path.tagName.toString().toLowerCase();
if (tagName == 'app-component')
inComponent = true;
}
});
if (inComponent) {
console.log('clicked inside');
}else{
console.log('clicked outside');
}
}
You can use the clickOutside() method from the ng-click-outside package; it offers a directive "for handling click events outside an element".
NB: This package is currently deprecated. See https://github.com/arkon/ng-sidebar/issues/229 for more info.
Another possible solution using event.stopPropagation():
define a click listener on the top most parent component which clears the click-inside variable
define a click listener on the child component which first calls the event.stopPropagation() and then sets the click-inside variable
You can call an event function like (focusout) or (blur); then you would put in your code:
<div tabindex=0 (blur)="outsideClick()">raw data </div>
outsideClick() {
alert('put your condition here');
}
nice and tidy with rxjs.
i used this for aggrid custom cell editor to detect clicks inside my custom cell editor.
private clickSubscription: Subscription | undefined;
public ngOnInit(): void {
this.clickSubscription = fromEvent(document, "click").subscribe(event => {
console.log("event: ", event.target);
if (!this.eRef.nativeElement.contains(event.target)) {
// ... click outside
} else {
// ... click inside
});
public ngOnDestroy(): void {
console.log("ON DESTROY");
this.clickSubscription?.unsubscribe();
}