Angular event won't fire when clicking on svg - html

I have an angular (4) based web app, with an #angular/material based card view. Embedded in the content of the card view is a sub-component, which just displays an svg as an <object>.
Here is what that card looks like:
<md-card (click)="onSelect(line)">
<md-card-content (click)="onSelect(line)">
<app-line-overview [line]="line"></app-line-overview>
</md-card-content>
<md-card-footer>
<h2>{{line.name}}</h2>
<h3>OEE: {{line.oee}}</h3>
</md-card-footer>
</md-card>
The issue is that the (click) event doesn't work if I click on the svg image (presumably because it is on top of the card view?), but if I click around the svg, the event fires.
I tried the adding md-card { z-index: 999 } to the css, but it makes no difference. How can I ensure that clicking anywhere within the card fires the event regardless of what is inside it?

Maybe writing a directive with a listener on clicks would help :
#Directive({
selector: '[clickInside]'
})
export class ClickInsideDirective {
constructor(private elementRef: ElementRef) {
}
#Output()
public clickInside = new EventEmitter<Event>();
#HostListener('click', ['$event', '$event.target'])
public onClick(event: MouseEvent, targetElement: HTMLElement): void {
if (!targetElement) {
return;
}
const clickedInside = this.elementRef.nativeElement.contains(targetElement);
if (clickedInside) {
this.clickOutside.emit(event);
}
}
...
}
usage
<md-card (clickInside)="onSelect(line)">

Related

Angular Page becomes white after adding MatDialog code

I was trying to use MatDialog to open a dialog with information in it. I followed a tutorial and in the component, I put a button that would call a "openDialog()" method and in the typescript I added the method along with changing the contructor. After changing the constructor, the page became completely blank, not showing a thing.
Here is the typescript with the changed constructor and method. (This is in the same component as the button in the html)
export class InfoComponent implements OnInit {
constructor(private dialog: MatDialog) {}
openDialog() {
this.dialog.open(BenefitsPopupComponent);
}
ngOnInit(): void {
}
}
Here is the basic button html
<button (click)="openDialog()">test</button>
and here is the component html that it will open
<h2 mat-dialog-title> test </h2>
the typescript on the component that is being opened is empty
I've tried various tutorials and such but keep getting the same results. I believe it has to do with the constructor but im not entirely sure. Thanks for the help!
this is my first time answering a question so I will try my best.
I'm not really sure if you mean that you want the popup to open a seperate component but I would rather do it as follows:
My Appomponent.ts looks like this:
export class AppComponent implements OnInit {
constructor(private dialog: MatDialog) { }
#ViewChild('myDialog', {static: false}) myDialog!: TemplateRef<any>;
openDialog() {
this.dialog.open(this.myDialog);
}
ngOnInit(): void {
}
doSomeAction() {
console.log('log something')
this.dialog.closeAll()
}
}
And then my html code looks like this:
<button (click)="openDialog()">test</button>
<ng-template #myDialog>
<h2 matDialogTitle>Test</h2>
<mat-dialog-content>
<p>Add some text here
</p>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button matDialogClose>Cancel</button>
<button mat-button (click)="doSomeAction()">Do Something</button>
</mat-dialog-actions>
</ng-template>
This is just the way I would approach it, not sure if this actually helps you or not.

Adding to component constructor in Angular makes the entire page return blank?

I am trying to add a basic MatDialog to my project. In the project I have 2 components, a header for the page and another called "CardBox", which basically just holds cardboxes of links to different websites.
When you click on the "i" icon, I would like to open a dialog box with more information.
See image below.
Initially, my understanding was that I just add a MatDialog field in the constructor of Cardbox component. Like so:
cardboxes.component.html
<mat-card id="CARDBOX">
<img class="info" src="path/image.jpg" alt="image" height=25px (click)="openDialog()"/>
</mat-card>
cardboxes.component.ts
#Component({
selector: 'app-cardbox',
templateUrl: './cardbox.component.html',
styleUrls: ['./cardbox.component.scss']
})
export class CardboxComponent implements OnInit {
constructor(private dialog: MatDialog) { }
ngOnInit(): void {}
openDialog() {
this.dialog.open(CardBoxComponent);
}
}
(I'm aware that this is calling its own component, and would just open the same thing again. I am just trying to get it to work first.)
app.component.html
<div id="bg">
<app-header></app-header>
<br>
<app-cardbox></app-cardbox>
</div>
However, in doing so, it removes EVERYTHING from the page except the background, including the header component. This is what it looks like when the program is run when there is SOMETHING in the constructor of Cardbox.
As you can see, having something in the constructor gets rid of everything on the page, which does not make sense to me as it removes the header, which is a completely separate component from the cardbox. I have tried everything to make it work but still it is not working.
Why is touching the constructor makes the entire project blank? Is there something I forgot to add to another file? And how can I add a MatDialog popup feature to the project in a way that works?
TLDR: When I put anything in the constructor of one of my components, the entire page disappears. How do I resolve this?
Still seeking answer to this :(
You are using it wrong.
I am surprised your app compiles when doing this.dialog.open(CardBoxComponent)
What you need to do is, first create your dialog component.
To make things simple you can create it in the same file as you CardBox component, but make sure you put it outside CardBox class:
cardboxes.component.ts
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: 'dialog-overview-example-dialog.html',
})
export class DialogOverviewExampleDialog {
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
// data is gonna be the data you pass to dialog when you open it from CardBox
#Inject(MAT_DIALOG_DATA) public data: DialogData) {}
onNoClick(): void {
this.dialogRef.close();
}
}
then you create a template for the dialog component:
dialog-overview-example-dialog.html
<h1 mat-dialog-title>more info</h1>
<div mat-dialog-content>
<p>{{data.info}}</p>
</div>
finally you add openDialog(myInfo) function to your ts file, inside CardBox component:
cardboxes.component.ts
openDialog(myInfo): void {
const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '250px',
// data you pass to your dialog
data: {info: myInfo}
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.animal = result;
});
}
and add it to your template too:
cardboxes.component.ts
<mat-card id="CARDBOX">
<img class="info" src="path/image.jpg" alt="image" height=25px (click)="openDialog('info about first site')"/>
</mat-card>
in this example I pass the info as a text, but it can be an object too.
Here is a demo to make things easier for you: link

Angular - Prevent click event on disabled buttons

I'm trying to prevent click event on disabled buttons, in other words, prevent some user who removes the disabled attribute to call some action.
For now, I have the following code to do this:
<button [disabled]="someCondition" (click)="executeAction()">Execute action</button>
executeAction(): void {
if (this.someCondition) return;
// ...
}
Works, but it isn't a good solution as I have to do it for ALL buttons in my app (and believe me, it's easy to forgot to do this and even a Linter can't help me here).
Looking for a more robust solution, I thought that directive could help me:
import { Directive, HostListener, Input, Renderer2, ElementRef } from '#angular/core';
#Directive({
selector: 'button'
})
export class ButtonDirective {
#Input() set disabled(value: boolean) {
this._disabled = value != null;
this.renderer2.setAttribute(this.elementRef.nativeElement, 'disabled', `${this._disabled}`);
}
private _disabled: boolean;
constructor(
private readonly elementRef: ElementRef,
private readonly renderer2: Renderer2
) { }
#HostListener('click', ['$event'])
onClick(mouseEvent: MouseEvent) {
// nothing here does what I'm expecting
if (this._disabled) {
mouseEvent.preventDefault();
mouseEvent.stopImmediatePropagation();
mouseEvent.stopPropagation();
return false; // just for test
}
}
}
<button [disabled]="someCondition" (click)="executeAction()">Execute action</button>
executeAction(): void {
console.log('still being called');
}
...however it does absolutely nothing. It doesn't prevent the click event. Is there any solution that I don't have to control the action itself in its call?
STACKBLITZ
This is a workaround with CSS which cheaper than scripts.
You easily could use
pointer-events: none;
In this case, the button will not be clickable.
As a UX enhance you could also wrap your button inside a div and give this div a CSS property
cursor: not-allowed;
Which will show the blocked circle icon instead of normal mouse view when hover.
In your directive, you can do something like this. You can achieve it by adding an event listener to parent in the capturing phase.
ngOnInit() {
this.elementRef.nativeElement.parentElement.addEventListener('click',(e) => {
if(this._disabled && e.target.tagName === 'BUTTON') {
e.stopImmediatePropagation();
e.stopPropagation();
}
}, true);
}
You can remove the listener in onDestroy
Prevent click event on disabled buttons
If the disabled attribute is there the click will not happen.
When user decides to use devtools
However if the user edits the HTML and removes the disabled attribute manually, then click will happen. You can try and do the check as you have suggested, but the browser is an unsafe environment. The user will still be able to execute any code on the webpages behalf irrespective of any frontend checks you might put in.

Theme service that affects all components and modals <ng-template let-modal> in Angular 6+

According to the post Toggle a class by a button from header component that affect main app component by Angular 6+
I created a theme service, it works fine for all components by toggling class 'dark-mode' in app.component.html, until I found out that all of my modals <ng-template let-modal> are under the <body>, of course these modals don't work.
So I turned to another solution from this answer: stackoverflow.com/a/59123790/6630524
I tried to inject DOCUMENT and Renderer2 into the header component and it works fine. But I see now the theme service is deprecated, so I put Renderer2 to the theme service (not in header component anymore), and it refused to work!
Could you help me to retain the theme service, and still be able to apply the theme to all the components (and these modals as well)?
It turned out no one answered for quite some time, so I decided to dig into the solution myself, see below FYI.
First, still keep using the theme service by putting Renderer2 into the theme.service.ts (previously set locally in header component):
#Injectable({
providedIn: 'root'
})
export class ThemeService {
public isDarkMode: boolean;
private renderer: Renderer2;
constructor(
#Inject(DOCUMENT) private document: Document,
rendererFactory: RendererFactory2
) { this.renderer = rendererFactory.createRenderer(null, null);
}
public toggleDarkMode() {
this.isDarkMode = !this.isDarkMode;
if (this.isDarkMode) {
this.renderer.addClass(this.document.body, 'dark-mode');
} else {
this.renderer.removeClass(this.document.body, 'dark-mode');
}
}
}
Second, you need to set a public variable in a constructor, here I choose the constructor of header.component.ts:
export class HeaderComponent implements OnInit {
/*
* Inject the theme service which will be called by our button (click).
* #param {ThemeService} themeService instance.
*/
constructor(public themeService: ThemeService) {}
ngOnInit() {}
}
Then put a button in the header.component.html to toggle your themes (Note: You can put the button anywhere in your project, along with the public variable defined in the corresponding abc.component.ts)
<button (click)="themeService.toggleDarkMode()"><i class="fa fa-moon"></i></button>
Now the public toggleDarkMode function is fired anytime the button clicked, then the modals and other components will reflect the change when theme service get updated by your defined dark-mode style, for example:
#lucky.dark-mode {
color: #a2b9c8;
background-color: #01263f!important;
}
And you might need this applied to all the <body> in index.html:
<body id="lucky">
<app-root></app-root>
</body>
Good luck and happy coding!

Binding to separate element on (click) in Angular 2

I have an html audio element that I would like to play when you click an image of a play button. I'm working in Angular 2 so it's a little more complicated than just using jQuery binding... Here's what I have so far:
#Component({
selector: 'listen',
template: `
<h1>Play Your Greeting!</h1>
<img src="../images/play.svg" (click)="playMusic($event)">
<audio controls src="../music/06 What Is The Light_.mp3">
`,
styles: [`
img {
width: 30%;
}
`]
})
export class ListenComponent {
constructor(
private listenService: ListenService,
private route: ActivatedRoute,
private router: Router
) {}
playMusic(event) {
console.log(event.target);
event.target.play();
}
}
Obviously the event is attaching to the image, but somehow I need the .play() to be called on the audio tag.
You can just assign a local variable to the audio element, then pass it to the method call
<img src="../images/play.svg" (click)="playMusic(audioEl)">
<audio #audioEl controls src="../music/06 What Is The Light_.mp3">
playMusic(el: HTMLAudioElement) {
el.play();
}
See the #audioEl. This is assigning the audio element to the template accessible variable. You can see it is being passed to the playMusic method call