How to add an Angular property to an HTML element - javascript

I need to know how to add to an html button the property (click) = function() of angular through Javascript.
Note: I cannot modify the HTML, I can only add the property through JavaScript.
I tested with addEventListener and it works by adding the common JavaScript click = "function" event, but not the (click) of Angular.
I attach the code:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-iframe',
templateUrl: './iframe.component.html',
styleUrls: ['./iframe.component.scss']
})
export class IframeComponent implements OnInit {
constructor() {}
ngOnInit() {
}
capture() {
let button = document.getElementById('cancelButton').addEventListener('(click)', this.cancel.bind(Event));
}
cancel() {
console.log('Cancelled');
}
}
And the HTML here:
<div class="row text-center pad-md">
<button id="acceptButton" mat-raised-button color="primary">OK!</button>
<button id="cancelButton" mat-raised-button>Cancel</button>
</div>

As stated by the author, the event need to be attached dynamically to the DOM element that is created after a request, so you can use Renderer2
to listen for the click event. Your code should look like this:
import { Component, OnInit, Renderer2 } from '#angular/core';
#Component({
selector: 'app-iframe',
templateUrl: './iframe.component.html',
styleUrls: ['./iframe.component.scss']
})
export class AppComponent implements OnInit {
name = 'Angular';
constructor(private renderer: Renderer2) {}
ngOnInit() {}
capture() {
const button = document.getElementById('cancelButton');
console.log(button);
this.renderer.listen(button, 'click', this.cancel);
}
cancel() {
console.log('Cancelled');
}
}
There's a functional example here.

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 6 Renderer.listen to Directive event

I am trying to listen to a directive event on an element. Renderer.listen is not picking up the event.
HTML code:
<a click-elsewhere #assignmentOptions>
<pp-click-dropdown [clickElement]="assignmentOptions">
<a>Test Button</a>
</pp-click-dropdown>
</a>
pp-click-dropdown.ts - clickElsewhere() is the directive event in the 'click-elsewhere' directive. This is event is emitting properly. I am not getting any errors, Renderer.listen is just not picking up the directive event.
import { Component, EventEmitter, Input, Output, Renderer, OnInit } from '#angular/core';
#Component({
selector: 'pp-click-dropdown',
templateUrl: 'panelply-click-dropdown.component.html',
})
export class PanelPlyClickDropdownComponent implements OnInit {
#Input() clickElement: Element;
#Output() onDropdownClick: EventEmitter<any> = new EventEmitter();
private renderer: Renderer;
constructor(_renderer: Renderer) {
this.renderer = _renderer;
}
ngOnInit() {
this.renderer.listen(this.clickElement, 'click', (event) => {
this.toggleDropDown(event);
}); // this line is working properly
this.renderer.listen(this.clickElement, 'clickElsewhere', (event) => {
console.log('clicked oustide')
this.closeDropdown(event);
}); // this line is not working
}
toggleDropDown(e) {
this.isShown = !this.isShown;
this.onDropdownClick.emit(e);
}
closeDropdown(event: Object) {
if (event && event['value'] === true) {
this.isShown = false;
}
}
}
click-elsewhere.ts (directive)
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output, HostListener } from '#angular/core';
import { Subscription } from 'rxjs';
#Directive({
selector: '[click-elsewhere]'
})
export class ClickElsewhereDirective implements OnInit, OnDestroy {
#Output() clickElsewhere: EventEmitter<Object>;
constructor(private _elRef: ElementRef) {
this.clickElsewhere = new EventEmitter();
}
#HostListener('document:click', ['$event.target'])
public onClick(targetElement) {
const clickedInside = this._elRef.nativeElement.contains(targetElement);
if (!clickedInside) {
console.log('clicked outside source')
this.clickElsewhere.emit(null);
}
}
}
Is there something I'm missing here? Does listen not work because directive events are not DOM events? Is there a way to do this?
This isn't really the way I wanted to do this because I want 'click-elsewhere' and 'pp-click-dropdown' to modular and I didn't want to potential run into issues later on different elements, but it works:
<a click-elsewhere (clickElsewhere)="dropdown.isShown = false;" #assignmentOptions>
<pp-click-dropdown #dropdown [clickElement]="assignmentOptions">
<a>Test Button</a>
</pp-click-dropdown>
</a>

Angular - How can I toggle the visibility of an element in a component from another component?

I have the following scenario in my Angular app:
A component MainDashboardComponent that is visible when I have the route /. Obviously I have the <router-outlet> tag in my app.component.html file, which looks like this:
<app-side-menu></app-side-menu>
<div class="main-container">
<div class="content">
<router-outlet></router-outlet>
</div>
</div>
As you can see I have a SideMenuComponent I use to have a side menu on all my routes. In MainDashboardComponent I have a method that for some reason needs to toggle a chat element that is situated on the side menu.
Inside the SideMenuComponent I have a method that handles the visibility toggle for the chat element and it works as expected. How can I call this method from my MainDashboardComponent and toggle the chat element from there?
What I tried with no success
I tried to inject the SideMenuComponent inside my MainDashboardComponent but, though the method toggleChat() is called, the element doesn't change it's visibility. Looks like I have a kind of multiple instance of the same component I guess...
Can you please help me with this? Thank you!
MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
templateUrl: './main-dashboard.component.html',
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() {}
setFocus(id) {
// here I'd like to call SideMenuComponent togglechat() ...
}
}
SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
}
}
To communicate between different components, there are different ways.
If you want to communicate between parent and child component, you can use EventEmitter to emit event from child component and handle the event in your parent component
If you want to communicate between any components, you can use Service and implement communication with the help of EventEmitter or Subject/BehaviorSubject
In your case, we can create a service, myService.ts and declare and eventEmitter
.service.ts
#Injectable()
export class AppCommonService {
toggle : EventEmitter<boolean> = new EventEmitter<boolean>()
}
mainDashboard.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
toggleChat(){
this.myService.toggle.emit(!this.chatStatus);
}
sideMenu.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
Generally this is the domain of a service!
Just create a service and add the "showCat" property.
Inject the service into both components
Alter SideMenuComponent to:
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
Alter MainDashboardComponent, also use this.myService.showChat to show / hide your chat window
Service TS
#Injectable()
export class MyService{
showCat:boolean = true
}
MainDashboardComponent
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
SideMenuComponent
chatVisiblity = this.myService.showCat //<-- bind this to the element attribute
You could efficiently use child to parent communication in this scenario. You'll need to create a custom event using angular's EventEmitter in your SideMenuComponent and use it in your MainDashboardComponent.
So, here is some code that may help you -
// SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
#Output() valueChange = new EventEmitter();
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
this.valueChange.emit(this.showChat);
}
}
// MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
template: `<app-side-menu (valueChange)='setFocus($event)'></app-side-menu>`
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() { }
setFocus(event) {
// check for required input value
console.log(event);
}
}
Refer these tutorials if required -
https://dzone.com/articles/understanding-output-and-eventemitter-in-angular,
https://angular-2-training-book.rangle.io/handout/components/app_structure/responding_to_component_events.html

Angular #Output not working

Trying to do child to parent communication with #Output event emitter but is no working
here is the child component
import { Component, OnInit, Output, Input, EventEmitter } from '#angular/core';
#Component({
selector: 'app-emiter',
templateUrl: './emiter.component.html',
styleUrls: ['./emiter.component.css']
})
export class EmiterComponent implements OnInit {
#Output() emitor: EventEmitter<any>
constructor() { this.emitor = new EventEmitter()}
touchHere(){this.emitor.emit('Should Work');
console.log('<><><><>',this.emitor) // this comes empty
}
ngOnInit() {
}
}
this is the html template
<p>
<button (click)=" touchHere()" class="btn btn-success btn-block">touch</button>
</p>
The console.log inside the touchHere it shows nothing
even if I put this inside the parent component it show nothing as well
parent component
import { Component , OnInit} from '#angular/core';
// service I use for other stuff//
import { SenderService } from './sender.service';
// I dont know if I have to import this but did it just in case
import { EmiterComponent } from './emiter/emiter.component'
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
user: any;
touchThis(message: string) {
console.log('Not working: ${message}');
}
constructor(private mySessionService: SenderService) { }
}
and here is the html template
<div>
<app-emiter>(touchHere)='touchThis($event)'</app-emiter>
</div>
Parent component template:
<app-emitor (emitor)='touchThis($event)'></app-emiter>
In parent template #Output should be 'called', not the child method.
Also, see: https://angular.io/guide/component-interaction#parent-listens-for-child-event
Here’s an example of how we write a component that has outputs:
#Component({
selector: 'single-component',
template: `<button (click)="liked()">Like it?</button>`
})
class SingleComponent {
#Output() putRingOnIt: EventEmitter<string>;
constructor() {
this.putRingOnIt = new EventEmitter();
}
liked(): void {
this.putRingOnIt.emit("oh oh oh");
}
}
Notice that we did all three steps: 1. specified outputs, 2. created an EventEmitter that we attached
to the output property putRingOnIt and 3. Emitted an event when liked is called.
If we wanted to use this output in a parent component we could do something like this:
#Component({
selector: 'club',
template: `
<div>
<single-component
(putRingOnIt)="ringWasPlaced($event)"
></single-component>
</div>`
})
class ClubComponent {
ringWasPlaced(message: string) { console.log(`Put your hands up: ${message}`);
} }
// logged -> "Put your hands up: oh oh oh"
Again, notice that:
putRingOnIt comes from the outputs of SingleComponent
ringWasPlaced is a function on the ClubComponent
$event contains the thing that wasemitted, in this case a string
<app-emiter (emitor)="touchThis($event)" ></app-emiter>
By using #Output() you should apply the event you need to emit in the directive of the emitter component.Adding the name of the variable to the the directive and but the emitted over function inside the quotation passing the $event.
touchHere() is the method from which you are binding some value to emit with your EventEmitter. And your EventEmitter is 'emitor'.
So your code will work if you simply do the below:
<app-emiter (emitor)='touchThis($event)'></app-emiter>

Angular2 on focus event to add class

I'm looking to update an Angular 1 app to Angular 2 and am having an issue with one of my old directives.
The idea is simple. When an input field is focused a class should be added (md-input-focus) and another be removed (md-input-wrapper). Then this process should be reversed on "blur" event - i.e. focus lost.
My old directive simply included the lines
.directive('mdInput',[
'$timeout',
function ($timeout) {
return {
restrict: 'A',
scope: {
ngModel: '='
},
link: function (scope, elem, attrs) {
var $elem = $(elem);
$elem.on('focus', function() {
$elem.closest('.md-input-wrapper').addClass('md-input-focus')
})
.on('blur', function() {
$(this).closest('.md-input-wrapper').removeClass('md-input-focus');
})
}
etc...
I obviously have the classic start to my directive but have run out of.....skill
import {Directive, ElementRef, Renderer, Input} from 'angular2/core';
#Directive({
selector: '.mdInput',
})
export class MaterialDesignDirective {
constructor(el: ElementRef, renderer: Renderer) {
// Insert inciteful code here to do the above
}
}
Any help would be appreciated.
UPDATE:
The HTML would look like (before the input element was focused):
<div class="md-input-wrapper">
<input type="text" class="md-input">
</div>
and then
<div class="md-input-wrapper md-input-focus">
<input type="text" class="md-input">
</div>
after.
The input element is the only one which can receive a focus event (and therefore the target for the directive) however the parent <div> requires the class addition and removal.
Further help
Please see Plunker for help/explanation - would be great if someone could help
Update
#Directive({selector: '.md-input', host: {
'(focus)': 'setInputFocus(true)',
'(blur)': 'setInputFocus(false)',
}})
class MaterialDesignDirective {
MaterialDesignDirective(private _elementRef: ElementRef, private _renderer: Renderer) {}
setInputFocus(isSet: boolean): void {
this.renderer.setElementClass(this.elementRef.nativeElement.parentElement, 'md-input-focus', isSet);
}
}
Original
This can easily be done without ElementRef and Renderer (what you should strive for in Angular2) by defining host bindings:
import {Directive, ElementRef, Renderer, Input} from 'angular2/core';
#Directive({
selector: '.mdInput',
host: {
'(focus)':'_onFocus()',
'(blur)':'_onBlur()',
'[class.md-input-focus]':'inputFocusClass'
}
})
export class MaterialDesignDirective {
inputFocusClass: bool = false;
_onFocus() {
this.inputFocusClass = true;
}
_onBlur() {
this.inputFocusClass = false;
}
}
or a bit more terse
#Directive({
selector: '.mdInput',
host: {
'(focus)':'_setInputFocus(true)',
'(blur)':'_setInputFocus(false)',
'[class.md-input-focus]':'inputFocusClass'
}
})
export class MaterialDesignDirective {
inputFocusClass: bool = false;
_setInputFocus(isFocus:bool) {
this.inputFocusClass = isFocus;
}
}
I tried it only in Dart where it works fine. I hope I translated it correctly to TS.
Don't forget to add the class to the directives: of the element where you use the directive.
In addition to previous answers, if you don't want to add a directive, for the specific component (you already have a directive for a parent component, you are using Ionic 2 page or something else), you inject the renderer by adding private _renderer: Renderer in the page constructor and update the element using the event target like this:
html:
(dragstart)="dragStart($event)"
TS:
dragStart(ev){
this._renderer.setElementClass(ev.target, "myClass", true)
}
Edit: to remove the class just do:
dragEnd(ev){
this._renderer.setElementClass(ev.target, "myClass", false)
}
The name of the selector has to be inside "[ ]", as shown below
#Directive({
selector: '[.mdInput]',
host: {
'(focus)':'_setInputFocus(true)',
'(blur)':'_setInputFocus(false)',
'[class.md-input-focus]':'inputFocusClass'
}
})
If you want to catch the focus / blur events dynamiclly on every input on your component :
import { AfterViewInit, Component, ElementRef } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit {
name = 'Angular focus / blur Events';
constructor(private el: ElementRef) {
}
ngAfterViewInit() {
// document.getElementsByTagName('input') : to gell all Docuement imputs
const inputList = [].slice.call((<HTMLElement>this.el.nativeElement).getElementsByTagName('input'));
inputList.forEach((input: HTMLElement) => {
input.addEventListener('focus', () => {
input.setAttribute('placeholder', 'focused');
});
input.addEventListener('blur', () => {
input.removeAttribute('placeholder');
});
});
}
}
Checkout the full code here : https://stackblitz.com/edit/angular-wtwpjr

Categories