In my application I have a button with a click even on it:
<button class="btn btn-default" (click)="doSomething()">
From within the doSomething method, is there any way to remove the (click) event from the button (so the user can't trigger the functionnality anymore?).
I tried to set a disabled prop on the button but it doesn't change Angular2 behavior.
I tryed to use (click)="doSomething($event) and then
doSomething($event) {
// My method logic goes here
...
...
console.log('Method Logic');
//Attempt to overwrite click event
let target = event.target || event.srcElement || event.currentTarget;
this.renderer.listen(target, 'click', (event) => {
console.log('clic block');
});
}
But It doesn't "replace" the click event. So after that, on click, both original logic and the "click block" console log are triggered!
Method 1:
You can set a boolean variable, so if the user calls the function, boolean value changes and the user will be able to call the function on click again but actually nothing will happen.
bool: boolean = true;
doSomething($event) {
if (this.bool) {
// My method logic goes here
...
...
console.log('Method Logic');
this.bool = false;
}
}
Method 2:
You can add a condition to your html component, if specified variable (in this case bool) is true, the function is going to be executed, but only once because the bool variable will be set to false and the click function will execute nothing (null) from now on.
bool: boolean = true;
doSomething($event) {
// My method logic goes here
...
...
console.log('Method Logic');
this.bool = false;
}
(click)="bool ? doSomething($event) : null"
The downside of just adding a guard variable for the execution is that the actual event listener is still in place and will trigger Angular's change detection when clicked, even though it doesn't do anything.
To actually remove the event listener, you have to add it via the component's Renderer. This will return a function that removes the event listener when called:
import {Component, AfterViewInit, Renderer, ViewChild, ElementRef} from '#angular/core';
#Component({
template: `<button #button>...</button>`
})
export class SampleComponent implements AfterViewInit {
#ViewChild('button') button: ElementRef;
private cancelClick: Function;
constructor(private renderer: Renderer) {}
ngAfterViewInit() {
this.cancelClick = this.renderer.listen(this.button.nativeElement, 'click',
($event: any) => this.handleClick($event));
}
handleClick($event: any) {
this.cancelClick();
// ...
}
}
If your goal is to just remove the event listener when the event is fired for the first time, this can be implemented as a plugin to Angular's event system. I added it as a part of my ng2-events utility library [source], which lets you now do the following:
<button (once.click)="handleClick($event)">...</button>
For people dealing with Observable / Subject for handling some events :
<button (click)="clickEvents$.next($event)">
class MyComponent {
clickEvents$ = new Subject<MouseEvent>();
firstClick$ = this.clickEvents.take(1); // Observable of first click
}
Related
Hi have simple radio buttons of primeng, while changing the choise, I have some logic on the onClick function, checking if the user changed some fields, if he changed I will show message if he sure he want to leave the choice of the radio button, if he will press "cancel" I want to cancel all the event of the onlick function and to undo to his last choise. but the event of the onclick not doing it, I checked all the function of java script. I tried now to add HostListener that if some boolean field(the one that said the user want to undo)it will stopImmediatePropagation. but on runtime the onclick function called and not the HostListener. some ideas what to do?
radio button
<p-radioButton name="treesDetailsType" [(ngModel)]="selectedType" formControlName="selectedType" (onClick)="onChangeType(type,$event)" class="treeDetails" value="{{type.id}}" label="{{type.desc}}" [disabled]="isReadOnly && type.id != data.selectedType"></p-radioButton>
the onclick function
onChangeType(type, event) {
let change = this.checkChanges(type, event);
if (change) {
//HERE I WANT TO CANCEL ALL THE CHANGE AND TO LEAVE THE FUNCTION
this.clickDisallowed = true;
}
else {
switch (type.id)
.....
}
}
the host listner
#HostListener('click', ['$event']) onClick(event) {
if (this.clickDisallowed) {
event.stopImmediatePropagation();
}
console.log(event);
}
Maybe you are using the hostlistener wrongly.
In the angular example it is used inside a directive
https://angular.io/api/core/HostListener
Try this example!
#Directive({selector: 'button[counting]'})
class CountClicks {
numberOfClicks = 0;
#HostListener('click', ['$event.target'])
onClick(btn) {
console.log('button', btn, 'number of clicks:', this.numberOfClicks++);
}
}
#Component({
selector: 'app',
template: '<button counting>Increment</button>',
})
class App {}
I am using innerHTML binding to create dynamic a tag as in below code:
<span *ngIf="msg" [innerHTML]="msg | sanitizeHtml"></span>
In .ts I am trying to add click event using addEventListerner:
ngAfterViewInit() {
this.elements = this.elem.nativeElement.querySelectorAll('.tradebtn');
if (this.elements && this.elements.length > 0) {
this.elements.forEach((f) => {
console.log(f)
f.addEventListener('click', (event) => {
console.log(event)
});
});
}
}
I get elementselements` list to add event listener. Click event listener works sometimes but doesn't work at most of the times.
I am perplexed at this behavior. I also tried to enclose the code setTimeout() but no luck.
You should use #HostListener to handle event.
Add condition event.target.matches('.tradebtn') to check element source.
#HostListener('document:click', ['$event'])
onclick(event) {
if(event.target.matches('.tradebtn')) {
console.log(event)
}
}
I want to expand and contract child div component depending on the value of a variable, but I want to be able to click out of that component (in the parent of the sibling) as well as collapsing it.
Here's an stackblitz example. I have attempted to use HostListener from what I found in this question, but it did not help my case.
#HostListener('document:click', ['$event'])
documentClick(event: MouseEvent) {
// your click logic
}
Objectives:
When I click on the child (hello) component, I want it to expand if it is not already and contract if it is already expanded
When I click on anything else (ie. parent or sibling component), I want the child (hello) component to contract if it is expanded.
I do not want the child (hello) component to expand when clicking in the parent/sibling.
Update: Using HostListener
hello.component.html
<div class="box" (click)="clicked()" [ngClass]="{large: isEnlarged}">Hello.component</div>
hello.component.ts
export class HelloComponent {
isEnlarged = false;
clicked() {
this.isEnlarged = !this.isEnlarged;
}
#HostListener('document:click', ['$event'])
documentClick(event: MouseEvent) {
console.log('clicked');
this.isEnlarged = false;
}
}
app.component
export class AppComponent {
}
The problem is your click handler is setting expanded to true before document click event handler sets it to false, so it's always false.
You could only set it to false if the event target is not your component:
https://stackblitz.com/edit/mouse-click-anywhere-8bwg6p?file=src/app/hello.component.ts
#HostListener('document:click', ['$event'])
documentClick(event: MouseEvent) {
console.log('clicked');
console.log(event);
if (event.target.id !== 'box') {
this.isEnlarged = false;
}
}
How does ngModelChange() works?
(ngModelChange) is the #Output of ngModel directive. It fires when the model changes. You cannot use this event without ngModel directive
but I am not sure, how does(ngModelChange) it works, if I am use ngModelChange() event, even i am not providing model name to ngModel.
<input #gb type="text" pInputText class="ui-widget ui-text" **ngModel**
(ngModelChange)="functionName($event)">
Yes, ngModelChange() work without providing model name to ngModel.
cause of this happen, (ngModelChange) is the #Output of ngModel directive.
when insert some value in input that time emitEvent is become true which is by default false (so it not fire page load on initial time).
_this.updateValueAndValidity({ emitEvent: false });
you can find at \#angular\forms\esm5\forms.js ► line no 3850
If emitEvent is true, this
change will cause a valueChanges event on the FormControl to be emitted. This defaults
to true (as it falls through to updateValueAndValidity).
If emitViewToModelChange is true, an ngModelChange event will be fired to update the
model. This is the default behavior if emitViewToModelChange is not specified.
If emitModelToViewChange is true, the view will be notified about the new value
via an onChange event.
now question is that why get same value in $event which is inserted in input instead of ture, that cause
FormControl.prototype.setValue = /**
function (value, options) {
var _this = this;
if (options === void 0) { options = {}; }
(/** #type {?} */ (this)).value = this._pendingValue = value;
if (this._onChange.length && options.emitModelToViewChange !== false) {
this._onChange.forEach(function (changeFn) { return changeFn(_this.value, options.emitViewToModelChange !== false); });
}
this.updateValueAndValidity(options);
};
same file line no 3911 to 3919
In the Source code ngModelChange is just an event emitter.
#Output('ngModelChange') update = new EventEmitter();
It fires when the viewToModelUpdate function is executed.
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
this.update.emit(newValue);
}
ngModel can be anything and does not have a direct link to anything else. In the code it is declared and it is only used in a function called ngOnChanges
#Input('ngModel') model: any;
ngOnChanges(changes: SimpleChanges) {
this._checkForErrors();
if (!this._registered) this._setUpControl();
if ('isDisabled' in changes) {
this._updateDisabled(changes);
}
if (isPropertyUpdated(changes, this.viewModel)) {
this._updateValue(this.model);
this.viewModel = this.model;
}
}
I could be wrong here but it looks to me that ngModel is not the single source of truth but this.viewModel seems to be, because of this ngModel does not need a value for ngModelChange to work as it opporates seporatetly from the ngModel value.
Hope this helps.
you try it without ngModel
<select (change)="changed($event)">
<option *ngFor="let data of allData" [value]="data.id">
{{data.name}}
</option>
</select>
changed(e){
//event comes as parameter and then find data manually
//by using e.target.data
}
OR BY ID
<inputtype="text" #byid (change)="onChange(byid.value)" />
onChange(title:string){
alert(title);
}
You can try by passing id into input
I have a Call To Action (CTA) which has a ripple effect, provided by a custom element called click-ripple, similar to Google Material Design. The custom element called click-ripple has a rule in the CSS to prevent this element from being clickable:
pointer-events: none;
If I do not add this rule, the element will be on top of its parent and it will not link the user through to the correct page or it will not perform the right action. Is there a way to feed an event from the parent through to one of its children without too much hassle?
EDIT
Let's say there is a button made up of a anchor-tag on the page. The markup of that anchor tag would look like this:
<template>
<a href="https://www.google.com">
<click-ripple></click-ripple>
</a>
</template>
My question is: what is an efficient way to feed a click action from the anchor tag forward to the click-ripple element?
The answer is to add an event listener to the parent of the current element. Looking it up in the W3 specification I came to the conclusion that I need to use element.parentElement.
clickripple.js
//Shortened for everyone's sanity
export class ClickRipple {
constructor(CssAnimator, Element) {
this.animator = animator;
this.element = element;
}
attached() {
this.element.parentElement.addEventListener("click", (event) => {
this.playAnimation(event);
});
}
playAnimation(event) {
//Math based on event
//Animation with Aurelia's CssAnimator
}
}
A common way of adding behavior (such as a click ripple) to an element is to use a custom attribute: <parent click-ripple></parent>.
Otherwise here's how you could pass the events to an arbitrary element:
click-through-custom-attribute.js
import {inject} from 'aurelia-framework';
#inject(Element)
export class ClickThroughCustomAttribute {
constructor(element) {
this.element = element;
this.handler = event => this.value.dispatchEvent(event);
}
attached() {
this.element.addEventListener('click', this.handler);
}
detached() {
this.element.removeEventListener('click', this.handler);
}
}
<require from="click-through-custom-attribute"></require>
<button ref="button1>I'm Behind</button>
<button click-through.bind="button1">I'm In Front</button>
If you know that the element you want to pass the click to is always going to be the parent element you could do something like this inside of your click-ripple custom element:
import {inject} from 'aurelia-framework';
#inject(Element)
export class ClickRipple {
constructor(element) {
this.element = element;
this.handler = event => this.element.parentElement.dispatchEvent(event);
}
attached() {
this.element.addEventListener('click', this.handler);
... add click ripple behavior ...
}
detached() {
... remove click ripple behavior ...
this.element.removeEventListener('click', this.handler);
}
}