I want to close multi-select drop-down popup when user click outside the popup. It's working fine when user click outside of IFrame. But when user click on iframe popup did't got closed. I am trying to add some patch code but for that I need to detect click event on Iframe. I seen too many example but did't got fine solution.
#HostListener('click', ['$event.target'])
onClick() {
console.log('iframe clicked');
}
I have tried above code but onClick method didn't call on iframe click.
Note: I need to detect every click event not only first click.
You can try this Angular directive:
import {
Directive,
ElementRef,
OnInit,
Renderer2,
Input,
Output,
EventEmitter,
HostListener
} from '#angular/core';
#Directive({
selector: '[appIframeTracker]'
})
export class IframeTrackerDirective implements OnInit {
private iframeMouseOver: boolean;
#Input() debug: boolean;
#Output() iframeClick = new EventEmitter<ElementRef>();
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit(): void {
this.renderer.listen(window, 'blur', () => this.onWindowBlur());
}
#HostListener('mouseover')
private onIframeMouseOver() {
this.log('Iframe mouse over');
this.iframeMouseOver = true;
this.resetFocusOnWindow();
}
#HostListener('mouseout')
private onIframeMouseOut() {
this.log('Iframe mouse out');
this.iframeMouseOver = false;
this.resetFocusOnWindow();
}
private onWindowBlur() {
if (this.iframeMouseOver) {
this.log('WOW! Iframe click!!!');
this.resetFocusOnWindow();
this.iframeClick.emit(this.el);
}
}
private resetFocusOnWindow() {
setTimeout(() => {
this.log('reset focus to window');
window.focus();
}, 100);
}
private log(message: string) {
if (this.debug) {
console.log(message);
}
}
}
It emits an output event when we click on IFrame.
Source: https://gist.github.com/micdenny/db03a814eaf4cd8abf95f77885d9316f
I hope it will help.
Related
I try to call some electron api from angular and respone on it. For test i try to hide block:
electron.ts:
import {app,BrowserWindow,BrowserWindowConstructorOptions, ipcMain} from 'electron'
let window:any = null
app.whenReady().then(()=>{
const options:BrowserWindowConstructorOptions = {
width:300,
height:600,
title:"test",
show:false,
webPreferences: {
nodeIntegration: true,
contextIsolation : false
}
}
window = new BrowserWindow(options)
window.loadFile('some path')
window.webContents.on('did-finish-load',()=>{
window.show()
ipcMain.on('test',(value)=>{
window.webContents.send('test2')
})
})
})
app-component.html:
<div *ngIf='__flag'><h1>Test</h1>
<button (click)='clickFunction()'>Test button</button>
</div>
app-component.ts:
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { ElectronService, NgxElectronModule } from 'ngx-electron';
#Component({
selector: 'app-component',
templateUrl: './app-component.html',
})
export class AppComponentComponent implements OnInit {
__flag:boolean = true
constructor(private route:Router, private elect:ElectronService) { }
ngOnInit(): void {
}
clickFunction(){
this.elect.ipcRenderer.send('test')
this.elect.ipcRenderer.on('test2',()=>{
this.__flag=false
console.log(this.__flag);
})
}
}
But div removes only if i clicked twice on button. Why it happens? And how can i fix that? I need to hide block then button clicked
You are only setting up the event listener(this.elect.ipcRenderer.on('test2') in your clickFunction after you've sent the message to the main process. Therefore the listener isn't listening yet when the call from the main process arrives. It works on the second click because the listener has been configured in the first click and therefore you get the message.
Move your event listener to the ngOnInit function and it should be fine.
ngOnInit(): void {
this.elect.ipcRenderer.on('test2',()=>{
this.__flag=false
console.log(this.__flag);
});
}
clickFunction(){
this.elect.ipcRenderer.send('test');
}
I have a problem using the valueChanges function of ngForm. When binding an Input variable to the form with [(ngModel)], the form gets called multiple times on page load.
Is there a good way to only detect user changes?
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.form.form.valueChanges.subscribe((value) => {
//Gets called multiple times on page load
});
}
}
Perhaps it will be sufficient to just check for dirty/touched state
From: https://angular.io/guide/form-validation
To prevent the validator from displaying errors before the user has a chance to edit the form, you should check for either the dirty or touched states in a control.
When the user changes the value in the watched field, the control is marked as "dirty".
When the user blurs the form control element, the control is marked as "touched".
I solved the problem:
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.form.form.valueChanges.subscribe((value) => {
if(this.form.form.dirty) {
//DO STUFF
}
});
}
}
Try this :
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
this.form.form.valueChanges.subscribe((value) => {
//Gets called multiple times on page load
});
}
ngAfterViewInit(): void {
}
}
I have an angular webapp using angular material. In my HTML I have two ButtonToggles that you can toggle with a simple click event handler which I handle myself. However, there should also be a way to toggle the buttons with a keyboard shortcut. I have a keypress event listener that correctly intercepts the keypress, but I have no idea how I can toggle the associated button because I can't pass it in to the event handler.
Here is my html
<mat-button-toggle-group [multiple]="schema.multi">
<mat-button-toggle *ngFor="let label of schema.labels"
(click)="toggleAnnotation(label, localButton)"
[value]="label.name"
#localButton
[style.background-color]="localButton.checked == true ? label.backgroundColor : 'ghostwhite'">
{{label.name}} ({{label.shortcut}})
</mat-button-toggle>
</mat-button-toggle-group>
</div>
And the related typescript:
import {Component, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges} from '#angular/core';
import {Label} from '../sequence/models/label';
import {TagAssignment} from '../../../models/tag-assignment';
import {MatButtonToggle, MatButtonToggleGroup} from "#angular/material/button-toggle";
export class CategoricalTaggerSchema {
multi: boolean; // can the user select multiple tags at once
prompt: string; // message to display before showing the tagger document
labels: Label[]; // categories to select from
}
#Component({
selector: 'app-categorical',
templateUrl: './categorical-tagger.component.html',
styleUrls: ['./categorical-tagger.component.css']
})
export class CategoricalTaggerComponent implements OnChanges {
#Input() config: TagAssignment = new TagAssignment(); // default to some value
#Output() valueChange = new EventEmitter<string>();
#Output() validChange = new EventEmitter<boolean>();
#Input() disabled = false;
schema: CategoricalTaggerSchema = {multi: false, prompt: '', labels: []};
annotations = new Set<string>(); // much simpler then sequence tagging, just a list of named labels
constructor() {}
ngOnChanges(changes: SimpleChanges): void {
if (changes.hasOwnProperty('config')) {
this.schema = JSON.parse(this.config.tag_schema);
}
}
toggleAnnotation(label: Label, localButton) {
if (!this.disabled) {
if (this.annotations.has(label.name)) {
this.annotations.delete(label.name);
localButton.checked = false;
} else { // creating new annotation
if (!this.schema.multi) { // only one annotation allowed
this.annotations.clear();
}
this.annotations.add(label.name);
}
}
this.emitChanges();
console.log(this.annotations);
}
emitChanges() {
this.valueChange.emit(this.getValue());
this.validChange.emit(this.isValid());
}
#HostListener('document:keypress', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
// detect keypress for shortcut
for (const label of this.schema.labels) {
if (label.shortcut === event.key) {
this.toggleAnnotation(label, null);
break;
}
}
}
getValue(): string {
return JSON.stringify(Array.from(this.annotations));
}
isValid(): boolean {
return (this.annotations.size > 0);
}
reset(): void {
this.annotations.clear();
}
}
The only thing I can think of is somehow fire a function from the HTML on component load that adds all the toggle buttons to an array or map which the TS has access to, and search them up by shortcut when I need them, but this seems like a hacky solution.
EDIT: I've tried using ViewChild, but since I can't initialize the ids dynamically (angular viewChild for dynamic elements inside ngFor) i cannot access the components to modify their checked state.
I have the following directive groupingFormat which perform grouping to an input text
when user use the key up:
#Directive({
selector: '[groupingFormat]'
})
export class GroupingFormatDirective {
private el: HTMLInputElement;
constructor(elRef: ElementRef) {
this.el = elRef.nativeElement;
}
ngAfterViewInit(): void {
let elem : HTMLInputElement = this.el;
elem.addEventListener('keyup',() => {
this.el.value = this.digitGrouping(this.el.value);
});
}
}
Example of usage:
<input type="text" #myValue="ngModel" name="my_value" [(ngModel)]="myObj.myValue" id="my_value" required groupingFormat>
This directive is working as expected but now I have new requirement: The input
text should use the directive also when the page is load and also if the a form
is open inside the page with the input becoming visible.
Is there an easy way to update the directive to support this functionality or
any alternative solution? Attach another directive ?
Thanks.
<input type="text" name="my_value" [appInputevent]="myValue" [(ngModel)]="myValue">
directive file
import { Directive,HostListener,ElementRef, Input } from '#angular/core';
#Directive({
selector: '[appInputevent]'
})
export class InputeventDirective {
constructor(private el:ElementRef) { }
#Input('appInputevent') params: string;
#HostListener('keyup', ['$event'])
onKeyUp(event: KeyboardEvent) {
console.log('got parameters: '+this.params);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
change hostlistner event according to your need.
For demonstration-- https://stackblitz.com/edit/ionic2-test?file=app%2Finputevent.directive.ts
Dynamically Setting the #HostListener's Arguments
I have a directive which needs to listen for any event provided declaratively by the engineer. Here's an example:
import { Directive, Input, ElementRef, HostListener, OnInit } from '#angular/core';
//--
import { Sandbox } from '../../../sandbox';
#Directive({ selector: '[addItem]' })
class AddNewItemDirective implements OnInit {
#Input('addItem') data;
#Input() on: string = 'click';
constructor(private $: Sandbox, private element: ElementRef) { }
ngOnInit() { console.log('#INIT', this); }
#HostListener('click', ['$event']) handleEvent(e) {
console.log('add-item', e);
}
}
export { AddNewItemDirective };
Here's its usage:
<button class="btn btn-primary" [addItem]="{ name: 'Jeffrey' }" on="focus">Add New Item</button>
This works fine. However, my intuition told me I should be able to dynamically set the HostListener's arguments at render time based upon an input parameter:
#Directive({ selector: '[addItem]' })
class AddNewItemDirective implements OnInit {
#Input('addItem') data;
#Input() on: string = 'click';
constructor(private $: Sandbox, private element: ElementRef) { }
ngOnInit() { console.log('#INIT', this); }
#HostListener(this.on, ['$event']) handleEvent(e) {
console.log('add-item', e);
}
}
Of course, this.on would not be overwritten with 'focus' until the time ngOnInit is invoked. To my surprise, this.on throws an error because undefined has no property 'on'. So when my directive class is instantiated, for whatever reason, this === undefined.
I found one similar question here, though, its looking to dynamically modify HostListener at runtime while I just need it modified at compile/render/instantiation time.
Can someone please shed light on how I can accomplish this?
Thx
HostListener is not dynamic, it can not be changed at runtime. You should use Renderer class, which provides listen method:
#Input()
public on:string;
private dispose:Function;
constructor(private renderer:Renderer, private elementRef:ElementRef){}
ngOnInit(){
this.dispose = this.renderer.listen(this.elementRef.nativeElement, this.on, e => console.log(e));
}
ngOnDestroy(){
this.dispose();
}