Bind button click event from interface value - javascript

I want to populate the button click from interface value for example
interface IButton {
buttonClickValue: string
}
export class Button implements OnInit {
toolBar: IButton = {
buttonClickValue: "onCML" //I also tried this onCML()
}
constructor() {}
ngOnInit(): void {
}
onCML(){
alert("Alert should appear on click!!!");
}
}
Here is my frontEnd button where I would like to bind the interface value to the button click
<a class="btn btn-primary" (click)="toolBar.buttonClickValue">My Button</a>
However when I click on the button nothing happens I would like to see the alert. Thank you for your help.

The return type for buttonClickValue should match the function.
interface IButton {
buttonClickValue: () => void;
}
export class Button implements OnInit {
toolBar: IButton = {
buttonClickValue: this.onCML,
};
constructor() {}
ngOnInit(): void {}
onCML() {
alert("Alert should appear on click!!!");
}
}
Edit:
I believe you'll also need to invoke said function in the template:
<a class="btn btn-primary" (click)="toolBar.buttonClickValue()">My Button</a>

Define a callback type to the buttonClickValue property instead of string.
Assign the onCML callback using this.onCML or an arrow function. If you intend to use this keyword inside the callback to refer to class member variables, you either need to bind using this.onCML.bind(this) or use an arrow-function (Canonical answer).
import { Component, OnInit } from "#angular/core";
interface IButton {
buttonClickValue: () => void;
}
#Component({
selector: "app-button",
template: `
<a class="btn btn-primary" (click)="toolBar.buttonClickValue()">
My Button
</a>
`
})
export class ButtonComponent implements OnInit {
toolBar: IButton = {
buttonClickValue: () => this.onCML()
};
constructor() {}
ngOnInit(): void {}
onCML() {
alert("Alert should appear on click!!!");
}
}
Working example: Stackblitz

Related

Click eventListener binding not working on dynamic HTML Angular 12

i have an issue while click binding on dynamic html.I tried setTimeout function but click event not binding on button.i have also tried template referance on button and get value with #ViewChildren but #ViewChildren showing null value.
Typscript
export class AddSectionComponent implements OnInit {
sectionList: any = [];
constructor(private elRef: ElementRef,private _httpService: CommonService ,private sanitized: DomSanitizer) { }
ngOnInit(): void {
this.getSectionList();
}
ngAfterViewInit() {
let element = this.elRef.nativeElement.querySelector('button');
if (element) {
element.addEventListener('click', this.bindMethod.bind(this));
}
}
bindMethod() {
console.log('clicked');
}
sanitizeHtml(value: string): SafeHtml {
return this.sanitized.bypassSecurityTrustHtml(value)
}
getSectionList() {
//API request
this._httpService.get('/Section/GetSectionList').subscribe(res => {
if (res) {
this.sectionList = res.json();
//sectionList is returning below HTML
//<div class="wrapper">
// <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
//</div>
}
})
}
}
Template
<ng-container *ngFor="let item of sectionList">
<div [innerHTML]="sanitizeHtml(item?.sectionBody)">
</div>
//innerHTML after rendering showing this
//<div class="wrapper">
// <button type='button' class='btn btn-primary btn-sm'>Click Me</button>
//</div>
</ng-container>
Short Answer, you are binding functions inside your templates, which means you have a new html content every time change detection runs, and change detection runs everytime a function is called, which means your button keeps on being updated infinitely, that's why it never works, Read more here please.
Now on how to do this, I would listen to ngDoCheck, and check if my button has a listener, if not, I will append the listener. I will also make sure to use on Push change detection, because if not, this will ngDoCheck will be called a lot, and maybe the button will be replaced more often, not quite sure about it.
Here is how the code would look like.
html
<!-- no more binding to a function directly -->
<div #test [innerHTML]='sanitizedHtml'></div>
component
import { HttpClient } from '#angular/common/http';
import { AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DoCheck, ElementRef, OnDestroy, ViewChild } from '#angular/core';
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements DoCheck {
name = 'Angular';
people: any;
//now we are getting the div itself, notice the #test in the html part
#ViewChild('test')
html!: ElementRef<HTMLDivElement>;
//a property to hold the html content
sanitizedHtml!: SafeHtml;
constructor(private _http: HttpClient, private sanitized: DomSanitizer,private change: ChangeDetectorRef ) {}
ngDoCheck(): void {
//run with every change detection, check if the div content now has a button and attach the click event
if (this.html != undefined) {
let btn = this.html.nativeElement.querySelector('button');
if (btn && btn.onclick == undefined) {
btn.onclick = this.bindMethod.bind(this);
}
}
}
ngOnInit() {
this.peoples();
}
peoples() {
this._http.get('https://swapi.dev/api/people/1').subscribe((item: any) => {
const people = `<div class="wrapper">
<p>${item['name']}</p>
<button type='button' class='btn btn-primary btn-sm'>Click Me</button>
</div>`;
//assign the html content and notify change detection
this.sanitizedHtml = this.sanitized.bypassSecurityTrustHtml(people);
this.change.markForCheck();
});
}
bindMethod() {
console.log('clicked');
}
}
I don't like the approach because of the need to listen to ngDoCheck, this can run a lot, especially if you don't use onpush change detection.
I hope this helped.

Angular - Pass function as input

I have a modalComponent that I create dynamically.
<div class="modal">
<div class="modal-body">
Test
</div>
<div class="modal-footer">
<button (click)="callbackFunction()">success</button>
<button>abort</button>
</div>
</div>
This component has an Input callbackFunction that'a function that I want to invoke from my parent component.
import {
Component,
Input,
OnInit,
QueryList,
ViewChildren
} from "#angular/core";
import { ModalService } from "../modal.service";
#Component({
selector: "app-modal",
templateUrl: "./modal.component.html",
styleUrls: ["./modal.component.css"]
})
export class ModalComponent implements OnInit {
#Input() callbackFunction: () => void;
constructor(private modalService: ModalService) {}
ngOnInit() {}
}
After that I created a service:
import {
ApplicationRef,
ComponentFactoryResolver,
ComponentRef,
Injectable,
Injector
} from "#angular/core";
import { ModalComponent } from "./modal/modal.component";
#Injectable()
export class ModalService {
dialogComponentRef: ComponentRef<ModalComponent>;
open(callbackFunction: any) {
const modalComponentFactory = this.cfResolver.resolveComponentFactory(ModalComponent);
const modalComponent = modalComponentFactory.create(this.injector);
modalComponent.instance.callbackFunction = callbackFunction;
this.dialogComponentRef = modalComponent;
document.body.appendChild(modalComponent.location.nativeElement);
this.appRef.attachView(modalComponent.hostView);
}
close() {
this.appRef.detachView(this.dialogComponentRef.hostView);
}
constructor(
private appRef: ApplicationRef,
private cfResolver: ComponentFactoryResolver,
private injector: Injector
) {}
}
After componentFactoryResolver I pass my function as instance.
In my parent controller I create a function
sayHello(
this.myService.doSomething();
}
and after that I create a function for opening a modal
open(this.sayHello());
When I click on the button and I invoke callback function, "this" is not referred to Parent component but to Modal Component and sayHello is undefined. How can I fix this situation?
I don't want to use emit.
This is my stackblitz: Example
Basically there are three solutions for this: Output + EventEmitter, #ViewChild and Subject
ViewChild solution
This one can be used when the button is defined on the Parent and you want to get something from the Child.
///////parent.component.ts
...
import { ChildComponent } from 'child/child.component';
...
export class ParentComponent {
#ViewChild(ChildComponent) childComponent: ChildComponent;
public buttonClick(): void {
let childResponse = this.childComponent.getValues();//will return '1234'
...
}
}
///////child.component.ts
export class ChildComponent {
valueInsideChild = '1234';
public getValues(): string {
return this.valueInsideChild;
}
}
Output + EventEmitter solution
In this scenario the child itself sends something to the parent(aka the button is inside the child)
implementation on stackblic
//////parent.component.html
<child-selector
($buttonClicked)=clickAction($event)>
</child-selector>
//////parent.component.ts
...
export class ParentComponent {
public clickAction(value: string): void {
console.log(value);//will log 'something1234 when child button is clicked
}
}
//////child.component.ts
...
import { Output, Component, EventEmitter } from '#angular/core';
...
export class ChildComponent {
#Output() $buttonClicked = new EventEmitter<string>();
public click(): void {
this.$buttonClicked.emit('something1234');
}
}
//////child.component.html
<button (click)="click()">
Subject
Interface responses using your modalService+subject+observables
///app.component.ts
...
export class AppComponent {
...
open() {
//subscribe to the observable :)
this.modalService.open(this.sayHello).subscribe(response => {
alert(response.text);
});
}
...
}
///modal.component.html
...
<button (click)="click()">success</button>
...
///modal.component.ts
...
export class ModalComponent {
constructor(private modalService: ModalService) {}
...
public click(): void {
this.modalService.close({text: 'Hello World'});
}
}
///modal.service.ts
...
import { Subject, Observable } from 'rxjs';
...
export class ModalService {
...
private _modalResponse = new Subject<any>();
...
open(): Observable<any> {//this is your open function
...
return this._modalResponse.asObservable();//return an observable where the modal responses will be emitted
}
close(response: any): void {
//receives a value from the modal component when closing
this.appRef.detachView(this.dialogComponenRef.hostView);
this._modalResponse.next(response);//emit the response on the Observable return when open was called
}
}
I suggest you to use an Output and a EventEmitter to call the parent component function from the child component, Angular documentation provides a good example on how to do it.
https://angular.io/guide/inputs-outputs#sending-data-to-a-parent-component

Angular: Check when Output Variable in Component Changes?

How do I check if Output in Component changes? Then run another method
Here is Parent component,
After it gets data from Child, want to immediately run another event.
Parent HTML:
<div>
Address Type:*
<app-address-type-dropdown (selectedItemOutput) = "test"></app-address-type-dropdown>
</div>
Parent Typescript:
Goal: When value is outputted, detect changes in this parent, and write console command.
export class AddressFormatheaderFormComponent implements OnInit {
constructor() { }
public test: any;
public sayHi(){
console.log(this.test);
}
ngOnInit() {
}
}
You can do so by creating another function, an event handler essentially. So when your child component <app-address-type-dropdown> emits a value, this event handler will take care of what to do next.
E.g. onNewItemSelect($event) is the event handler.
<div>
Address Type:*
<app-address-type-dropdown (selectedItemOutput)="onNewItemSelect($event)"></app-address-type-dropdown>
</div>
export class AddressFormatheaderFormComponent implements OnInit {
constructor() { }
public test: any;
ngOnInit() {
}
onNewItemSelect(itemSelected){
this.test = itemSelected;
console.log(this.test);
//do something else
}
}
Do have read on this section of of Angular official docs on component interaction for more information.
I think you will need EventEmitter so in child component, when value changes, it will emit event and in parent component, it will detect changes and call parent function.
Something like let's say your child component.
import { Component, EventEmitter, Output } from '#angular/core';
#Component({
selector: 'app-address-type-dropdown,
template: `<button class='btn btn-primary' (click)="valueChanged()">Click me</button> `
})
export class AppAddressTypeDropdown{
#Output() selectedItemOutput= new EventEmitter();
Counter = 0;
valueChanged() { // You can give any function name
this.counter = this.counter + 1;
this.selectedItemOutput.emit(this.counter);
}
}
And in parent html, just try update call slightly. Please call any function to know when it changes.
<app-address-type-dropdown (selectedItemOutput) = "changeDetect($event)"></app-address-type-dropdown>
export class AddressFormatheaderFormComponent implements OnInit {
constructor() { }
public test: any;
public sayHi(){
console.log(this.test);
}
ngOnInit() {
}
changeDetect(counter){
console.log(counter);
//do something here
}

function variable dynamic setting

This question related to Syntactically anonymous/Arrow Function/add-hoc/factory DP functions:
I have a component which is embedded in the Html.
The component has a click event which is binded to a function. This function content depend on another component which has a reference to this component.
This is the component with the click event:
HTML:
<div (click)="doSomething()">Content.....</div> \\ Should it be with a brackets ?
In the component I just want to define the function signature:
#Component({
selector: 'app-embedded'
})
export class className
{
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) => any; //The function get a boolean parameter as input and return void or any
}
Now this is where the component is embedded:
<div >
<app-embedded #emb></app-embedded>
</div>
This is the component of the container of the embedded component, which has a reference to the embedded component:
#Component({
selector: 'app-container',
})
export class container
{
#ViewChild('emb') private emb: ElementRef;
booleanParam : booelan;
constructor()
{
emb.doSomething = containerFunction(true);
}
containerFunction(booleanParam : boolean)
{
// do something in this context
}
}
The idea is that this embedded component is embedded in many other containers and whenever the click event triggered a function that was set in the doSomething function variable should be executed.
What changes in the code I need to do in order to accomplish this ?
The best way i see of doing this would be to simply use an event emitter and capture the event on the other side? so embedded would have this:
#Component({
selector: 'app-embedded'
})
export class className
{
#Output()
public something: EventEmitter<boolean> = new EventEmitter<boolean>();
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) {
this.something.emit(booleanparams);
}; //The function get a boolean parameter as input and return void or any
}
Then where it is called:
<div >
<app-embedded #emb (something)="doSomething($event)"></app-embedded>
</div>
Other solution that would allow a return
#Component({
selector: 'app-embedded'
})
export class className
{
#Input()
public somethingFunc: (boolean)=>any;
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) {
let w_potato = this.somethingFunc(booleanparams);
//Do whatever you want with w_potato
}; //The function get a boolean parameter as input and return void or any
}
in this case the view would be
<div >
<app-embedded #emb [somethingFunc]="doSomething"></app-embedded>
</div>
I hope this helps! Passing the function or emitting an event will be much more angular than trying to modify an instance of a component. On top of that, a constructor is only called once when Angular starts up so #emb at that time will not be defined to be anything. If you wanted to do it that way you would have to bind yourself in something ngAfterViewInit.
But again, I think that passing it through attributes will be much more angular looking.
Good Luck let me know if this doesn't suit your answer.

Can you reference a function that's created within ngOnInit?

I was just wondering if there's any way, shape or form to reference a function that's created within ngOnInit(), or some sort of closure you can create to do so?
Basically:
component(){
somefunc()
//be able to call the function that's created in ngOnInit from the component via
//click event after the component renders
ngOnInit() {
function somefunc(){ ...whatever }
}
}
Is there any way to do this?
It can be done by assigning the method to a class member property in ngOnInit. In the code below, I define the method as an arrow function, to make sure that this refers to the instance of the component in the body of the method. See this stackblitz for a demo.
export class AppComponent implements OnInit {
public onButtonClick: (event: Event) => void; // Member property will refer to the method
ngOnInit() {
let data = "And this comes from the closure!"; // Can be used inside of onButtonClick
// Assign the method to the member property
this.onButtonClick = (event: Event): void => {
console.log("The button was clicked!", data);
...
};
}
}
The method can then be used as an event handler:
<button (click)="onButtonClick($event)">Click me!</button>
Your pseudo syntax is a bit confusing.
You can call a created function like this:
import { Component, OnInit } from '#angular/core';
#Component({
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
showImage = false;
constructor() {
}
ngOnInit(): void {
this.toggleImage();
}
toggleImage(): void {
this.showImage = !this.showImage;
}
}
You can also call it from a click event on a button like this:
<button class='btn btn-primary'
(click)='toggleImage()'>
Show Image
</button>
Is this what you are asking?

Categories