Angular material Modal dialog not able to pass event to parent? - angular6

I have a component which has one child component. Child component has a button which will open a material dialog Box.
In dialog we have form, username and passwrod and submit button. When I submit i am calling backend REST api.
this is getting called in child component:
dialogRef.afterClosed().subscribe(result => {
console.log("result", result);
this.onModalClosePassData.emit(result);//emit to parent
});
which is sending event to parent. updateComment() is getting called and I can see the data in console.
But when I fill the form and click on submit. It calls submitForm method which is asynchronus call and I am closing dialog after successful login.But then event is not emmiting. updateComment() is not getting called.
See the full code:
parent component.html
<ng-template #showTextOnly>
<child-component [branch]="releaseBranch" [date]="dateString"
(onModalClosePassData)="updateComment($event)">
</child-component>
</ng-template>
parent component.ts
//This method is getting called when i click on backDrop,
but If i logged in successfully this is not getting called
updateComment(event:any){
consile.log(event);
}
child-component.html
<button class="btn btn-default" (click)="openDialog()">Login</button>
child-component.ts
export class ChildComponent implements OnInit {
#Output() onModalClosePassData = new EventEmitter();
constructor(public dialog: MatDialog) { }
openDialog(): void {
const dialogConfig = new MatDialogConfig();
dialogConfig.disableClose = false;
dialogConfig.autoFocus = false;
dialogConfig.hasBackdrop= true;
dialogConfig.width = '300px';
dialogConfig.autoFocus=true;
dialogConfig.data = {branch: this.branch, date: this.date};
const dialogRef = this.dialog.open(LoginDialog, dialogConfig);
dialogRef.afterClosed().subscribe(result => {
console.log("result", result); //result is getting called in both cases
this.onModalClosePassData.emit(result);
});
}
}
LoginDialog.component.ts
import {MatDialogRef, MAT_DIALOG_DATA} from '#angular/material/dialog';
export class LoginDialog implements OnInit{
constructor(private loginService: LoginService, public dialogRef: MatDialogRef<LoginDialog>,
#Inject(MAT_DIALOG_DATA) public data: any) {}
public submitForm = (formValue: any) => {
if (this.noteForm.valid) {
let encryptData = btoa(`${formValue.username}:${formValue.password}`);
this.loginService.login(encryptData)
.subscribe((response:any)=>{
if(response.STATUS === "FAILED"){
} else {
this.dialogRef.close(this.noteDetail);
}
})
}
}
}
LoginDialog.component.html
<form [formGroup]="noteForm" autocomplete="off" novalidate (ngSubmit)="submitForm(noteForm.value)">
<mat-dialog-content class="mat-typography">
<mat-form-field>
<mat-label>User Name</mat-label>
<input matInput type="text" formControlName="username" id="username">
</mat-form-field>
<mat-form-field>
<mat-label>Password</mat-label>
<input matInput type="password" formControlName="password">
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions align="center">
<button mat-raised-button color="primary" [disabled]="!noteForm.valid">Submit</button>
</mat-dialog-actions>
</form>

I have faced same issue and figured it out, may be this is usefull for others.
We will get this issue when we used custom component on modal. For example, we have formComponent on pop up modal and on submit we need to close the modal and emit the form value, this should work but we can face the issue when our formComponent is destroyed before emitting the value.
This is because we opened our formComponent on Modal later on form submit we closed the modal which contains formComponent and opened success modal then trying to emit the value.
Solution is: Don't close modal which contains formComponent before emmiting the value or else use a service to trigger.

Related

Reactive form with dynamic data change from component

I am setting up a reactive form in angular 6, where I have 2 input boxes(one input is an optional entry) and a submit button. If I enter a value to one input box and press submit, I need to fill the other input box by setting corresponding value from component side. If I enter values to both input boxes, then another function is called. If so how is two-way data bindin possible in form controls? I tried using ngModel, which is not working as expected and from stackoverflow answers, came to know that using ngmodel with form controls is soon to be deprecated. How Can I achieve the same if so? Below is the code snippet I am using:
Component.ts:
export class myComponent implements OnInit {
converterForm: FormGroup;
model: myModel = new MyModel();
constructor(private formBuilder: FormBuilder, ) {
this.myForm = this.formBuilder.group({
vOne: [this.model.vOne],
vTwo: [this.model.vTwo],
});
}
onSubmit(searchInputs) {
this.model.vTwo= "new"; //I need to edit the form value and reflect it in html.. two-waybinding
this.converterForm.value.vOne = "edited";
console.log("Submit called");
}
}
html file:
<div>
<div>
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm.value)">
<div>
<div>
<mat-form-field>
<input id="vOne" matInput formControlName="vOne" [(ngModel)]="model.vOne">
</mat-form-field>
</div>
<div>
<mat-form-field>
<input id="vTwo" matInput formControlName="vTwo" [(ngModel)]="model.vTwo">
</mat-form-field>
</div>
</div>
<div>
<button mat-raised-button color="primary" type="submit" (click)="search()">
<mat-icon aria-label="Search icon">search </mat-icon>
Search
</button>
</div>
</form>
</div>
thanks in advance.
Using valueChanges for access to live changes, and using setValue func for setting value per input.
in ts file try:
export class myComponent implements OnInit {
myForm: FormGroup;
constructor(private formBuilder: FormBuilder) {
this.myForm = this.formBuilder.group({
vOne: [null],
vTwo: [null],
});
searchHandler();
}
searchHandler() {
const searchInputs = {
vOne: '',
vTwo: '',
};
for (const propertyName in searchInputs) {
const property = this.form.get(propertyName);
property.valueChanges
.subscribe((searchText) => {
// this.form.controls.vOne.setValue('whatever');
// this.form.controls.vTwo.setValue('whatever');
// searchText is what keypress in input tag
});
}
}
onSubmit() {
// this.form.controls.vOne.setValue('whatever');
// this.form.controls.vTwo.setValue('whatever');
}
}
in html file try:
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
<div>
<mat-form-field>
<input matInput formControlName="vOne">
</mat-form-field>
</div>
<div>
<mat-form-field>
<input matInput formControlName="vTwo">
</mat-form-field>
</div>
</form>

How to handle input events on custom input component in Angular?

I'm trying to create a custom Input component using Reative Form in Angular 7 but i'm having trouble passing events to the input field.
My component has a label and an input field and a specific layout. My component html looks like this:
<div id="customInputGroup" [formGroup]="parentFormGroup" [ngClass]="{'input-value': hasValue()}">
<label [for]="inputId">{{label}}</label>
<input [id]="inputId" [name]="inputName" class="form-control" [placeholder]="placeholder" [formControlName]="inputName" [mask]="mask">
</div>
And in my Component:
#Component({
selector: 'app-input-field',
templateUrl: './input-field.component.html',
styleUrls: ['./input-field.component.css']
})
export class InputFieldComponent implements OnInit {
#Input('placeholder') placeholder = '' ;
#Input('label') label = '';
#Input('inputId') inputId = '';
#Input('inputName') inputName = '';
#Input('parentFormGroup') parentFormGroup:FormGroup;
#Input('mask') mask;
constructor() { }
ngOnInit() {
}
hasValue(){
return (this.parentFormGroup.get(this.inputName).value != undefined
&& this.parentFormGroup.get(this.inputName).value != null
&& this.parentFormGroup.get(this.inputName).value != '')
}
Everything was working fine till i had to handle an input onBlur event(or it could be any other input event).
The result i want is to call my component passing any event like this:
<app-input-field (blur)="functionName()"></app-input-field>
or
<app-input-field (keyup)="functionName()"></app-input-field>
And my component will be able to pass these events to my input field 'dynamically'. Is it possible to do it?
Events like blur works on input field , not selectors like <app-input-field>
You can emit event for all events like blur, keyup, mouseover etc..
InputFieldComponent:
HTML:
<input (blur)="functionName('blur')" (keyup)="functionName('keyUp')" [id]="inputId" [name]="inputName" class="form-control" [placeholder]="placeholder" [formControlName]="inputName" [mask]="mask">
TS:
#Output() OnInputEvent= new EventEmitter();
functionName(eventName) {
this.OnInputEvent.emit(eventName);
}
In your component:
<app-input-field (OnInputEvent)="functionName($event)"></app-input-field>
TS:
functionName(event) {
switch (event) {
case "blur":
...
break;
case "keyUp":
...
break;
}
}
Working Demo
for blur event you need to create a eventemitter with the same name and emit this event on input elemnt blur emit
export class InputFieldComponent implements OnInit {
#Output() blur:EventEmitter<any> = new EventEmitter(); // 👈
constructor() { }
ngOnInit() {
}
}
template
<input type="text" (blur)="blur.emit($event)" >
app.template
<app-input-field (blur)="onBlur($event)" (keyup)="onKeyup($event)"></app-input-field>
we did this for the blur event because the event is not bubble where the keyup will work without create any custom event because it bubble.
demo 🔥🔥
you can implement two way data binding like this
#Input() value:EventEmitter<any> = new EventEmitter();
#Output() valueChange:EventEmitter<any> = new EventEmitter();
template
<app-input-field [(value)]="name" ></app-input-field>
demo 🌟🌟
any bubble event like input,keypress,keyup,keydown you can capture on the element itself or you can work around it like what we did on blur event.

Why doesnt (click) sends my data inside <button>, but (change) inside <input> does in Angular, HTML

I want to send some data to my Glassfish RESTful server. When I use (change) inside the input tag it activates the method and succesfully sends the data, but when I use (click) or (change) to activate the method, it doesn't do anything. I tried to seperate the Sending data method and the router method, but to no avail.
How can I solve this?
html file:
<div class="container py-5">
<div class="row">
<div class="col-md-12">
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card rounded-0">
<div class="card-header">
<h3 class="mb-0">Organize</h3>
</div>
<div class="form-group">
<label for="codes" class="m-2">Choose a restaurant:</label>
<form #f="ngForm">
<input
type="text"
list="codes"
class="m-2"
(change)="saveCode($event)"
>
<datalist id="codes">
<option *ngFor="let c of codeList" [value]="c.name">{{c.name}}</option>
</datalist>
<button
type="button"
class="btn btn-primary btn-lg float-none m-2"
id="btnAanmaken"
(click)="routerRed()"
>Aanmaken</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
typescript file:
import {Component, OnInit, ViewChild} from '#angular/core';
import {Router} from "#angular/router";
import {NgForm} from "#angular/forms";
import {HttpClient, HttpHeaders} from "#angular/common/http";
#Component({
selector: 'app-organize',
templateUrl: './sendinvite.component.html',
styleUrls: ['./sendinvite.component.css']
})
export class SendinviteComponent implements OnInit {
// public codeValue: string;
// List of restaurants
codeList = [
{ name: 'Mcdonalds', address: 'Kalverstraat 5' },
{ name: 'Kentucky Fried Chicken', address: 'Kalverstraat 4' },
{ name: 'Burger King', address: 'Kalverstraat 3' },
{ name: 'Dominos pizza', address: 'Kalverstraat 2' },
{ name: 'New York Pizza', address: 'Kalverstraat 1' }
];
// Assign empty value to id, name and address
#ViewChild('f', { static: true }) form: NgForm;
restaurant = {
name: ' ',
address: ' '
};
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
Authorization: 'my-auth-token'
})
};
constructor(private http: HttpClient, private router: Router) {
}
ngOnInit() {
}
// Method to post data to backend
public saveCode (e): void {
const name = e.target.value;
const list = this.codeList.filter(x => x.name === name)[0];
this.restaurant.name = list.name;
this.restaurant.address = list.address;
console.log(list.name);
console.log(list.address);
// Additional information to the server content type
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
// Making an array with the values of the restaurant
const data = {
name: list.name,
address: list.address
};
console.log(data);
// POST method
this.http.post('http://localhost:8080/aquadine-jee/resources/restaurant',
JSON.parse(JSON.stringify(data)) , httpOptions)
// wait till it gets posted to the backend
.subscribe( // subscribe to observable http.post
res => {
console.log("response" + " " + res); // log results otherwise log error
},
err => {
console.log('Error occured');
}
);
}
routerRed(){
this.router.navigateByUrl('/menu');
}
I tried to use:
<button
type="submit"
class="btn btn-primary btn-lg float-none m-2"
id="btnAanmaken"
routerLink="/menu"
(change)="saveCode($event)"
>Aanmaken</button>
and:
<button
type="submit"
class="btn btn-primary btn-lg float-none m-2"
id="btnAanmaken"
routerLink="/menu"
(click)="saveCode($event)"
>Aanmaken</button>
The (change) event inside the button is going to do nothing, the (click) is more appropiate, but the $event passed inside the click event is going to return the information about the click itself (mouse position, etc).
What you should do is, whenever click is called, get the value of the form and send that to the saveData function. (Maybe by doing something like saveData(this.f.value), but I can't tell with certainty)
I don't have experience with template driven forms, maybe it's worth for you to take a look at Reactive Forms, it is going to make your life easier IMHO
The (change) event will not be triggered on the click of a button as there is no "change" as such. You should use (click) instead. The reason why your saveCode() is not being invoked when you set both routerLink and (click) is because of the routerLink. When you click the button, Angular is navigating to /menu before it triggers your click event. You can navigate from your component in your saveCode() instead.
Since you also have an API call in your saveCode(), it would probably be better if you navigate on success as it might not make sense to re-route the user if the API call fails (especially if the new route depends on the saved data)
Try this in your code.
HTML
<button type="submit"
class="btn btn-primary btn-lg float-none m-2"
id="btnAanmaken"
(click)="saveCode($event)">
Aanmaken
</button>
component
.subscribe( // subscribe to observable http.post
res => {
console.log("response" + " " + res); // log results otherwise log error
this.router.navigateByUrl('/menu');
},
err => {
console.log('Error occured');
});
Edit: Rather than using $event to pass your values to the component, you can make use of the ngForm you have used in your code along with ngModel.
component
<input type="text"
list="codes"
class="m-2"
name="restaurantName"
ngModel
>
component
#ViewChild('f', { static: true }) form: NgForm;
...
public saveCode (): void {
const name = this.form.value.restaurantName;
...
}
Here is another example from the documentation on how to bind values to the form using ngForm and ngModel

How to change button label on click?

When I click on this button,I want label to change. HTML:
<button pButton type="button" label="Edit" (click) = "foo()" style="width:auto"></button>
For example : before - "Edit", click, after - "Save".
You can simply bind it to your component variable inside your <button> tag.
<button pButton type="button" (click)="foo()"> style="width:auto">
{{myLabel}}
</button>
and in your component class:
#Component({
templateUrl:'./mytemplate'
})
export class MyComponent implements OnInit {
myLabel:string;
ngOnInit() {
this.myLabel = 'Edit';
}
foo() {
this.myLabel = 'Save';
}
}
Here is a working plunker: https://plnkr.co/edit/8TOn8oN63pgJ7eA7h7tY?p=preview
In your component class
#Component({
templateUrl:'./mytemplate'
})
export class MyComponent implements OnInit {
myLabel:string;
ngOnInit() {
this.myLabel = 'Edit';
}
foo() {
this.myLabel = 'Save';
}
}
In your html
<button pButton type="button" [attr.label]="myLabel" (click)="foo()" style="width:auto"></button>
Note that the html syntax has changed to start using property binding, where the "label" attribute of the node associated with the button element is being updated with the value of the myLabel variable in the component.
Read more about template and property bindings in this guide
https://angular.io/guide/template-syntax#property-binding
As a side note, if your requirement is to change the text displayed on the button, I would use interpolation as below
<button pButton type="button" (click)="foo()" style="width:auto">{{myLabel}}</button>
See this plunkr for a working example https://plnkr.co/edit/wEXKxP88kcsLKuBcUUxZ?p=preview
You can bind attributes via [attr.myAttribute] directive and as in your case you have to use [attr.label] to bind a value to the label attribute.
Inside your component you can define a label property which gets toggled on click:
class MyComponent {
private labelStates = ['Edit', 'Save'];
public label: string = this.labelStates[0];
public toggleLabel() {
let index = +(this.label === this.labelStates[0]);
this.label = this.labelStates[index];
}
}
And use it for your button:
<button [attr.label]="label" (click)="toggleLabel()"></button>
In case you want to change the button text use this:
<button (click)="toggleLabel()">{{ label }}</button>

Angular 2 dialog popup with text field input

I have a simple popup that shows a message with OK button to release it, what I want is to add to this popup a text field that the user will need to type something in it and click OK to submit his answer.
currently it looks like this:
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<button md-button (click)="dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
constructor( public dialog: MdDialogRef<DialogComponent>) { }
}
and im using it like:
public showDialog(message: MessageBoxMessage) {
if (typeof message !== 'undefined') {
let dialogRef: MdDialogRef<DialogComponent> = this._dialog.open(DialogComponent);
dialogRef.componentInstance.title = message.title;
dialogRef.componentInstance.message = message.content;
}
}
how can I change it to have a popup with text-field and ok button tht pass me the value of the text-field?
You can create EventEmitter in your dialog:
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<mat-form-field class="example-full-width">
<input matInput placeholder="Favorite food" #input>
</mat-form-field>
<button mat-button (click)="onOk.emit(input.value); dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
onOk = new EventEmitter();
constructor( public dialog: MatDialogRef<ErrorDialogComponent>) { }
}
then subscribe to it in parent component
dialogRef.componentInstance.onOk.subscribe(result => {
this.resultFromDialog = result;
})
Plunker Example
Another way is passing value to MatDialog.close method
(click)="dialog.close(input.value)"
....
dialogRef.afterClosed().subscribe(result => {
this.resultFromDialog = result;
});
Plunker Example
You can bind the answer to a model like this :
#Component({
selector: 'dialog-component',
template: `<h2>{{title}}</h2>
<p>{{message}}</p>
<input type="text" name="data" [(ngModel)]="data">
<button md-button (click)="dialog.close()">OK</button>`
})
export class DialogComponent {
public title: string;
public message: string;
data: string;
constructor( public dialog: MdDialogRef<ErrorDialogComponent>) { }
}
And then bind the (click) to a function that sends your data.