I have a page in which I have a Run button.If I click on Run button a dialog box appears with two options Yes and No.If a user clicks Yes I want to display a mat-progress bar.
I am confused as to where to write the html code of mat-progress bar and where to call it from.
HTML Code:
<mat-toolbar>
<div class="col-md-offset-11">
<button
mat-raised-button
mat-hint="Execute Query on Whole DataSet"
color="primary"
(click)="executeOnFullData()"
>
Run
</button>
</div>
</mat-toolbar>
TypeScript Code:
executeOnFullData() {
const dialogRef = this.dialog.open(ConfirmJobRunComponent, {
});
dialogRef.afterClosed()
}
HTML Code for dialogBox:
<div class="card">
<div class="card-header"><h5 class="title">Confirm</h5></div>
<div class="content">
<h3 mat-dialog-title>
Are you sure you want to run Recommendation Settings on the entire
Dataset?
</h3>
<div mat-dialog-actions>
<button
mat-button
[mat-dialog-close]="true"
(click)="confirmSelection()"
>
Yes
</button>
<button mat-button (click)="onNoClick()">
Cancel
</button>
</div>
</div>
Typescript Code for DialogComponent
import { MAT_DIALOG_DATA, MatDialogRef } from "#angular/material";
import { Component, Inject } from "#angular/core";
import { RecommendationService } from "../../recommendation-
service.service";
#Component({
selector: "app-confirm-job-run",
templateUrl: "./confirm-job-run.component.html",
styleUrls: ["./confirm-job-run.component.scss"]
})
export class ConfirmJobRunComponent {
constructor(
public dialogRef: MatDialogRef<ConfirmJobRunComponent>,
#Inject(MAT_DIALOG_DATA) public data: any,
public dataService: RecommendationService
) {}
onNoClick(): void {
this.dialogRef.close();
}
confirmSelection(): void {}
}
You can just subscribe to afterClosed of your dialogRef and based on the result you get back from your dialog (clicking Yes returns true, clicking No returns false) you can then show a mat-progress and execute your business logic.
Here
is a stackblitz showing how this could look like. The mat-progress
is currently indeterminate and not waiting for something to complete,
that is up to you.
Template (in your component where the button is located)
<mat-progress-bar *ngIf="showMatProgress" mode="indeterminate"></mat-progress-bar>
Component for above template
showMatProgress: boolean = false;
executeOnFullData() {
const dialogRef = this.dialog.open(ConfirmJobRunComponent, {});
dialogRef.afterClosed().subscribe((result) => {
this.showMatProgress = result;
});
}
An in your dialog component
onNoClick(): void {
this.dialogRef.close(false);
}
confirmSelection(): void {
this.dialogRef.close(true);
}
Related
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.
I have a template like the one below:
card.component.html
<mat-card class="mat-elevation-z4">
<img mat-card-image src="{{ item.media_url }}" />
<mat-card-content class="custom">
<p>{{ item.caption }}</p>
</mat-card-content>
</mat-card>
It is a Material Card used inside a parent View that creates a grid with cards, each having a specific item.media_url and item.caption
events.component.ts
<div class="content">
<div fxLayout="row wrap" fxLayoutGap="16px grid">
<div
fxFlex="25%"
fxFlex.md="33%"
fxFlex.sm="50%"
fxFlex.xs="100%"
*ngFor="let item of events"
>
<app-card [item]="item" #myCard></app-card>
<button mat-button (click)="openDialog(myCard)">SCOPRI DI PIU'</button>
</div>
</div>
</div>
As you can see, inside the ngFor loop, below each card, there's a button that is supposed to open a dialog showing only one card at a time (the one above the clicked button in the grid).
I would like to put very the same card with its item.media_url and item.caption in this dialog, so I thought to use the data property of MatDialog to do this.
card.component.ts
import { Component, OnInit, Input, Inject, Optional } from '#angular/core';
import { MAT_DIALOG_DATA } from '#angular/material/dialog';
#Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {
#Input() item : any;
constructor(#Optional() #Inject(MAT_DIALOG_DATA) public data: any) { }
ngOnInit(): void {
}
}
events.component.ts
import { Component, OnInit } from '#angular/core';
import { EventsService } from '../../shared/services/events.service';
import { MatDialog } from '#angular/material/dialog';
import { CardComponent } from 'src/app/shared/card/card.component';
#Component({
selector: 'app-events',
templateUrl: './events.component.html',
styleUrls: ['./events.component.css'],
})
export class EventsComponent implements OnInit {
events: any[];
constructor(private eventsService: EventsService, public dialog: MatDialog) {}
ngOnInit(): void {
this.getEvents();
}
getEvents(): void {
this.eventsService.getEvents().subscribe((response: any) => {
this.events = response.data;
console.log(this.events);
});
}
openDialog(card: any) {
this.dialog.open(CardComponent, {
data: {
item: card,
},
});
}
}
However, when I do this, I need to pass data to this.dialog.open(), not item, nor card.
Right now I am getting the error ERROR TypeError: ctx.item is undefined (which I perfectly understand why I am getting).
Is there a way I can somehow "alias" data as item, or maybe a better way to do this?
To make it work we did the following:
openDialog(card: CardComponent) { <-- strongly type
console.log(card.item); <-- access item
this.dialog.open(CardComponent, {
data: {
item: card.item <-- pass it as param
}
});
}
next in card.component.ts set the 'item` property.
constructor(#Optional() #Inject(MAT_DIALOG_DATA) public data: any) {
if (data) { <-- only if data is injected
this.item = data.item; <-- set item
}
}
Working Stackblitz
Robert's coment under your question answers the template / component part.
As for the data alias question, the answer is no. data is a property of the MatDialogConfig class, as shown here and to the best of my knowledge, you can't change it.
I'm writing a code where when you click a button of an item, a side pannel opens, and you can see some data in it. I'm currently getting the error below when I click the preview button. Furthermore, I could not figure out the problem. What can be the problem?
Main Screen HTML:
<button mat-icon-button matTooltip="Preview" class="sidebar-toggle"
(click)="openSidebar('sticker-preview-sidebar', row.StickerData)">
<mat-icon>pageview</mat-icon>
</button>
<fuse-sidebar class="sidebar centra details-sidebar fuse-white" name="sticker-preview-sidebar" position="right">
<sticker-preview [StickerData]="previewStickerData"></sticker-preview>
</fuse-sidebar>
Main Screen TS:
previewStickerData: IStickerData;
openSidebar(name: string, stickerData: IStickerData): void {
this.previewStickerData = stickerData;
this._fuseSidebarService.getSidebar(name).open();
}
Side Bar HTML:
<div class="content fuse-white ml-24 mr-8 h-100-p" fusePerfectScrollbar>
<div class="label-list">
<p>
Sticker Info
<span>{{stickerData.StickerData}}</span>
</p>
</div>
Side Bar TS:
private _stickerData: IStickerData;
#Input()
set StickerData(prm: IStickerData) {
if (this._stickerData != prm) {
this._stickerData = prm;
}
}
get StickerData(): IStickerData {
return this._stickerData;
}
dataSource: MatTableDataSource<IStickerData>;
constructor(
private _router: Router,
private _fuseSidebarService: FuseSidebarService,
private _productionService: ProductionService) {
}
I'm using Angular 9, where I want to dynamically change data of a menu item when a person logs in. But instead, since the menu gets loaded along with the home page, when a person logs in, the data change is not reflected in the menu items until I refresh the page manually. I tried using Renderer 2, ChangeDetectorRef and ElementRef but failded to reload the menu automatically. Below I'm adding just the relevant elements since the actual component code is long. Ask me if you need to know anything else:
Html:
<div class="widget-text">
<a mat-button [matMenuTriggerFor]="accountMenu" #accountMenuTrigger="matMenuTrigger" *ngIf="!isLoggedIn">
<mat-icon>person</mat-icon>
<span fxShow="false" fxShow.gt-sm class="flag-menu-title">Account</span>
<mat-icon class="mat-icon-sm caret cur-icon">arrow_drop_down</mat-icon>
</a>
<mat-menu #accountMenu="matMenu" [overlapTrigger]="false" xPosition="before" class="app-dropdown">
<span>
<button mat-menu-item [routerLink]="['/admin/login']" routerLinkActive="router-link-active">
<mat-icon >person</mat-icon>
<span>Login</span>
</button>
<button mat-menu-item [routerLink]="['/admin/login']" routerLinkActive="router-link-active">
<mat-icon>person_add</mat-icon>
<span>Register</span>
</button>
</span>
</mat-menu>
<a mat-button [matMenuTriggerFor]="profileMenu" #profileMenuTrigger="matMenuTrigger" *ngIf="isLoggedIn">
<mat-icon>person</mat-icon>
<span fxShow="false" fxShow.gt-sm class="flag-menu-title">Howdy, {{name}}</span>
<mat-icon class="mat-icon-sm caret cur-icon">arrow_drop_down</mat-icon>
</a>
<mat-menu #profileMenu="matMenu" [overlapTrigger]="false" xPosition="before" class="app-dropdown">
<span>
<button mat-menu-item [routerLink]="['/admin/profile']" routerLinkActive="router-link-active">
<mat-icon >person</mat-icon>
<span>Profile</span>
</button>
<button mat-menu-item (click)="logout()">
<mat-icon>warning</mat-icon>
<span>Logout</span>
</button>
</span>
</mat-menu>
</div>
typescript:
public name;
public isLoggedIn = false;
constructor(public router: Router, private cartService: CartService, public sidenavMenuService:SidebarMenuService) {
this.checkLogin();
this.name = Cookie.get('userName');
}
public checkLogin(): any {
if(Cookie.get('authtoken')) {
this.isLoggedIn = true;
}
}
You don't need to make things complicated, when you logged in your logged in guard (i.e. auth guard).
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuardService implements CanActivate {
constructor(public auth: AuthService, public router: Router , private sideMenuService: SideMenuService) {}
canActivate(): boolean {
if (!this.auth.isAuthenticated()) {
this.sideMenuService.sideMenuData.next({...data}); // so here you can dispatch the side menu service data .
this.router.navigate(['dashboard']); // here after authentication it
will redirect to your dashboard
page
return false;
}
return true;
}
}
}
so after redirect when you land on the Dashboard Page , in the Dashboard component you have also inject the sideMenu Service and subscribe the BehaviourSubject menu data field .
public name;
public isLoggedIn = false; // here you don't need to check login
// because you come here from auth guard
constructor(public router: Router, private cartService: CartService,
public sidenavMenuService: SidebarMenuService) {
this.checkLogin(); // same no need to check login in each
component if you use auth guard
this.name = Cookie.get('userName');
}
public ngOnInit(){
this.sideMenuService.sideMenuData.subscribe((data)=>{
// hered you get the data dynamic , you can assign to any
// component field.
});
}
public checkLogin(): any {
if(Cookie.get('authtoken')) {
this.isLoggedIn = true;
}
}
so that's how whenever you login every time you dispatch some dynamic data and your behaviourSubject will get updated and where ever you subscribe like in Dashboard component you will get the dynamic data.
Hope it will help.
The constructor is executed only one time during the creation of the page.
constructor(public router: Router, private cartService: CartService, public sidenavMenuService:SidebarMenuService) {
this.checkLogin();
this.name = Cookie.get('userName');
}
Now, according to the code, if the cookie authtoken is not found during the construction, there is no way your app to know if that was created by another (login) process.
You should call the checkLogin function and the name assignment right after your login cocmpletes.
import {Component, Inject} from '#angular/core';
import {MdDialog, MdDialogRef, MD_DIALOG_DATA} from '#angular/material';
/**
* #title Dialog Overview
*/
#Component({
selector: 'dialog-overview-example',
templateUrl: 'dialog-overview-example.html'
})
export class DialogOverviewExample {
animal: string;
name: string;
constructor(public dialog: MdDialog) {}
openDialog(): void {
let dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '250px',
data: { name: this.name, animal: this.animal }
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.animal = result;
});
}
}
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: 'dialog-overview-example-dialog.html',
})
export class DialogOverviewExampleDialog {
constructor(
public dialogRef: MdDialogRef<DialogOverviewExampleDialog>,
#Inject(MD_DIALOG_DATA) public data: any) { }
onNoClick(): void {
this.dialogRef.close();
}
}
Let's say i have the code as above. And the result shown below:
Based on this picture, how can i determine if the user clicked OK or No thanks. I want to create a function for each event. tried dialogRefAfterClose but it runs no matter what button i click.
In your dialog html, dialog-overview-example-dialog.html, You can just add (click) event in your both button.
<div mat-dialog-actions>
<button mat-button (click)="clickOK()" tabindex="2">Ok</button>
<button mat-button (click)="clickNo()" tabindex="-1">No Thanks</button>
</div>
and you can close the dialog programmaticly:
clickNo() {
console.log('No button clicked');
this.dialogRef.close();
}
clickOk() {
console.log('Ok button clicked');
this.dialogRef.close();
}
Hi I noticed that you are using the exact same example from angular.material.
The option you asked for need to be handled by Yourself according to your purpose. As seen in the example there.
my example
In here the onNoClick() and the onOkClick() does the Job
Add the functions in compontent ts and bind it in the component html
Dialog Component TS
export class DialogOverviewExampleDialog {
constructor(
public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any) { }
onNoClick(): void {
alert("You clicked no.")
this.dialogRef.close();
}
onOkClick():void{
alert("You clicked Ok");
}
}
Dialog Component HTML
<h1 mat-dialog-title>Hi {{data.name}}</h1>
<div mat-dialog-content>
<p>What's your favorite animal?</p>
<mat-form-field>
<input matInput tabindex="1" [(ngModel)]="data.animal">
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onOkClick()" [mat-dialog-close]="data.animal" tabindex="2">Ok</button>
<button mat-button (click)="onNoClick()" tabindex="-1">No Thanks</button>
</div>
You can do that with single function, no need to add functions for more buttons
Like this :
<div mat-dialog-actions>
<button mat-button (click)="clickBtn(true)" tabindex="2">Ok</button>
<button mat-button (click)="clickBtn(false)" tabindex="-1">No Thanks</button>
</div>
clickBtn(status) {
if(status)
console.log('Ok button clicked');
else
console.log('No button clicked');
this.dialogRef.close();
}
<button mat-button (click)="onNoClick()" [mat-dialog-close]="data.SelectedBtn">No Thanks</button>
<button mat-button (click)="onOkClick()" [mat-dialog-close]="data.SelectedBtn" cdkFocusInitial>Ok</button>
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: './dialog.html',
})
export class DialogComponent {
constructor(
public dialogRef: MatDialogRef<DialogComponent>,
#Inject(MAT_DIALOG_DATA) public data: DialogData) { }
onNoClick(): void {
this.data.SelectedBtn = false;
this.dialogRef.close(this.data.SelectedBtn);
}
onOkClick(): void {
this.data.SelectedBtn = true;
this.dialogRef.close(this.data.SelectedBtn);
}
}