Binding with ngModel angular dialog - html

I have a project that I can edit with dialog angular, but the problem is when I open the edit dialog what is changed automatically shows me in my UI I want to change after I save because if I change and click cancel that stays changed. Below you can find a code when i inject data and save.
What I change in dialog immediately overwrites changes, I wan'T after I save these will change.
Here is the dialog when it will open for edit.
openprojecteditdialog(project) {
this.dialog.open(ProjectEditDialogComponent, {
data: project, disableClose: true});
}
This is the template of edit dialog:
<mat-dialog-content>
<mat-tab-group>
<mat-tab label="Project">
<div id="general-content">
<mat-input-container>
<label>*Name</label>
<input placeholder="" matInput [(ngModel)]="project.name">
</mat-input-container>
<br>
<mat-input-container>
<label>*Type</label>
<mat-select class="tab-content-item" placeholder="" matInput
[(ngModel)]="project.type">
<mat-option *ngFor="let type of projectsType; let i = index"
[value]="i">
{{type}}
</mat-option>
</mat-select>
</mat-input-container>
<br>
<mat-input-container>
<label>*State</label>
<mat-select class="tab-content-item" placeholder="" matInput
[(ngModel)]="project.state">
<mat-option *ngFor="let state of projectsState; let i
=index" [value]="i">
{{state}}
</mat-option>
</mat-select>
</mat-input-container>
</div>
</mat-tab>
</mat-tab-group>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close (click)="save()"
[disabled]="project.name.length === 0">Save</button>
<button mat-button mat-dialog-close>Cancel</button>
</mat-dialog-actions>
This is the TS file of edit dialog.
export class ProjectEditDialogComponent implements OnInit {
readonly projectsState = ProjectState;
readonly projectsType = ProjectType;
readonly level: string[] = [];
working = false;
newType = '';
newState = '';
constructor(
public store: Store<ApplicationState>,
public dialogRef: MatDialogRef<ProjectEditDialogComponent>[],
#Inject(MAT_DIALOG_DATA) public project: any) {
}
ngOnInit() {
}
save() {
if (this.project.name.length > 0) {
this.working = true;
this.project.ProjectType = this.newType;
this.project.ProjectState = this.newState;
this.store.dispatch(new UpsertProjectInternalAction(this.project));
}
}
}

You are passing the reference of original project in edit dialog. So it will reflect the changes even if you don't save. Create the copy of project data so that it will not reflect with original project . And after save update the field which you want of original project.
openprojecteditdialog(project) {
let editProject = Object.assign({}, project);
this.dialog.open(ProjectEditDialogComponent, {
data: editProject, disableClose: true});
}
and save function will be
save() {
if (this.editProject.name.length > 0) {
this.working = true;
this.project.ProjectType = this.editProject.newType;
this.project.ProjectState = this.editProject.newState;
this.store.dispatch(new UpsertProjectInternalAction(this.project));
}
}

Related

Clear mat-select selection Angular material

I have implemented mat-multi-select option. I have a search functionality and I am able to select multiple options at once. Post selection when I click generate button, I want to clear all the selections. I am able to clear the values from search bar but values are still selected if I open the multi select dropdown. How can I clear those values.
HTML Code
<mat-select [formControl]="cMultiCtrl" [multiple]="true" required (selectionChange)="selectionChange($event)">
<mat-option>
<ngx-mat-select-search placeholderLabel="Search" noEntriesFoundLabel="No Matching Value Found" [formControl]="cMultiFilterCtrl"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let val of filteredCMulti | async" [value]="val.value">
{{val.name}}
</mat-option>
</mat-select>
// the button on click of which I want to clear everything post API call
<button mat-raised-button (click)="generate()" class="download-button">Generate
</button>
TS Code
public filteredCMulti: ReplaySubject<any[]> = new ReplaySubject<any[]>(1);
public cMultiCtrl: FormControl = new FormControl();
ngOnInit() {
this.filteredCMulti.next(this.cvalues.slice());
this.cMultiFilterCtrl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterBanksMulti();
});
}
ngAfterViewInit() {
this.setInitialValue();
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
private setInitialValue() {
this.filteredCMulti
.pipe(take(1), takeUntil(this._onDestroy))
.subscribe(() => {
this.singleSelect.compareWith = (a, b) => a.id === b.id;
});
}
selectionChange(event){
this.cvalue = event.value;
}
private filterBanksMulti() {
if (!this.cvalues) {
return;
}
let search = this.cMultiFilterCtrl.value;
if (!search) {
this.filteredCMulti.next(this.cvalues.slice());
return;
} else {
search = search.toLowerCase();
}
// filter the banks
this.filteredCMulti.next(
this.cvalues.filter(bank => bank.name.toLowerCase().indexOf(search) > -1)
);
}
generate(){
let msg = '';
// some logic here
else{
this.commonService.showSnackBar("Value generated")
this.filteredCMulti = new ReplaySubject; // this clears the search bar but not values, they are still selected
this.table();
}}
})
}
Provide an element ref to your <mat-select>
<mat-select #matRef [formControl]="cMultiCtrl" [multiple]="true" required (selectionChange)="selectionChange($event)">
<mat-option>
<ngx-mat-select-search placeholderLabel="Search" noEntriesFoundLabel="No Matching Value Found" [formControl]="cMultiFilterCtrl"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let val of filteredCMulti | async" [value]="val.value">
{{val.name}}
</mat-option>
</mat-select>
And update your ts file like:
#ViewChild('matRef') matRef: MatSelect;
clear() {
this.matRef.options.forEach((data: MatOption) => data.deselect());
}
You can optimize clear() by conditionally calling deselect()

dropdown and radio button form array value is not populating while editing in angular

In my form dropdown and radio button value is not populating while editing. I have tried using FormArray. But is still not getting the value in the dropdown and radio button.
HTML
<form [formGroup]="staffRegistrationForm">
<mat-label>Role</mat-label>
<mat-select formControlName="roleCode" required>
<mat-option *ngFor="let item of roleList" [value]="item.roleCode">
<span> {{item.description}} </span>
</mat-option>
</mat-select>
<mat-label>STD/ City Code</mat-label>
<mat-select formControlName="homeTelephoneCityCode" required>
<mat-option *ngFor="let phoneExt of phoneExtRefList"
[value]="phoneExt.value">
<span>{{phoneExt.name}} </span>
</mat-option>
</mat-select>
<div class="radio-group">
<mat-label>Staff ID * </mat-label>
<mat-radio-group formControlName="staffId">
<mat-radio-button [value]="id" *ngFor="let id of staffIds">
<span> {{id}} </span>
</mat-radio-button>
</mat-radio-group>
</div>
</form>
My component is
export class StaffRegistrationComponent implements OnInit {
staffRegistrationObj: StaffRegistrationObj = new StaffRegistrationObj();
roleList: Array<Role>;
phoneExtRefList = PHONE_EXT_LIST;
staffRegistrationForm: FormGroup;
submitted = false;
staffIds = [];
edit = false;
constructor(
private formBuilder: FormBuilder,
private staffService: StaffService,
) {
this.staffRegistrationForm = this.formBuilder.group({
staffId: [null, [Validators.required]],
role: this.formBuilder.group({[]}),
homeTelephoneCityCode: [null, [Validators.required]],
});
this.staffIds = ['Email', 'Mobile'];
const id = this.activatedRoute.snapshot.paramMap.get('id');
if (id !== null) {
this.getStaffDetails(id);}}}
onSave() {
private getStaffDetails(id: string) {
this.staffService.getStaffDetails(id).subscribe(
(staff: StaffRegistrationObj) => this.editStaffDetails(staff));}
private editStaffDetails(staff: StaffRegistrationObj) {
this.staffRegistrationForm.patchValue({
staffId: staff.staffId,
role: staff.role,
homeTelephoneCityCode: staff.homeTelephoneCityCode,});}
Please help me how to populate the value.

How to fix a ExpressionChangedAfterItHasBeenCheckedError

I have an application with a mat-autocomplete element in it with cities as options. The cities come from a database. There is a button to add an input element. This is done by having a div element with a *ngFor property that loops over a property in the component.ts file. When the add-button is clicked an element is added to the property and so an input is added. But when i type in a city name in the first input and then try to add an input element i get the ExpressionChangedAfterItHasBeenCheckedError.
I have found that a lot of people get this error but i've tried all the given solutions but non of them of them seem to work. Like :
startWith(''),
delay(0),
map(value => this._filter(value))
);
ngAfterViewInit(){
this.cd.detectChanges();
}
var request = new CityCreationRequestModel();
request.name = "";
Promise.resolve(null).then(() => this.reportGroupCreationRequest.cityReportGroups.push(request));
and trying with timeOuts.
this is my html file :
<div class="container">
<mat-card>
<mat-card-header>
<mat-card-title>Voeg een reportgroep toe</mat-card-title>
<mat-card-subtitle>Geef emailadressen op die verbonden worden met de rapporten van bepaalde steden</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form (ngSubmit)="onSubmit()" #addReportGroup="ngForm" *ngIf="!isLoading">
<div *ngFor="let city of reportGroupCreationRequest.cityReportGroups" class="form-row">
<mat-form-field>
<input placeholder="Stad" required [(ngModel)]="city.name" type="text" matInput [formControl]="myControl" [matAutocomplete]="auto" name="cityInput">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let cityName of filteredOptions | async" [value]="cityName">
{{ cityName }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
<button mat-raised-button (click)="addCity()">Voeg een stad toe</button>
<div class="form-row">
<button mat-raised-button type="submit" [disabled]="!addReportGroup.valid" class="submitButton">Opslagen</button>
</div>
</form>
<mat-spinner [style.display]="isLoading ? 'block' : 'none'"></mat-spinner>
</mat-card-content>
</mat-card>
and this is my .ts file :
the filter if for the mat-autocomplete element
export class AddReportGroupComponent implements OnInit {
myControl = new FormControl();
filteredOptions: Observable<string[]>;
public reportGroupCreationRequest: ReportGroupCreationRequestModel = new ReportGroupCreationRequestModel();
public cities: Array<CityResponseModel>;
public cityNames: Array<string>;
private isLoading: boolean;
constructor(private reportGroupService: ReportGroupService,
private cd: ChangeDetectorRef) {
}
ngOnInit() {
this.isLoading = true;
this.reportGroupService.getCities().subscribe(result => {
this.cities = result;
this.cities.sort((a,b) => a.name.localeCompare(b.name));
this.cityNames = this.cities.map(c=>c.name);
}).add(()=> {
this.isLoading = false;
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
delay(0),
map(value => this._filter(value))
);
});
this.reportGroupCreationRequest.cityReportGroups.push(new CityCreationRequestModel());
this.reportGroupCreationRequest.emailReportGroups.push(new EmailCreationRequestModel());
}
//doesn't seem to do anything
ngAfterViewInit(){
this.cd.detectChanges();
}
private _filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.cityNames.filter(cn => cn.toLowerCase().indexOf(filterValue) === 0);
}
addCity(){
var request = new CityCreationRequestModel();
request.name = "";
Promise.resolve(null).then(() => this.reportGroupCreationRequest.cityReportGroups.push(request));
}
and these are my models that I use :
export class ReportGroupCreationRequestModel {
public cityReportGroups: Array<CityCreationRequestModel> = new Array<CityCreationRequestModel>();
public emailReportGroups: Array<EmailCreationRequestModel> = new Array<EmailCreationRequestModel>();
}
export class CityCreationRequestModel {
public name: string;
}
Thanks in advance
I found a solution for the problem I had :
I used both [(ngModel)] and [FormsControl] and apparantly the [(ngModel)] was part of the problem so I only used [FormsControl] and the error is gone.
addCity(){
this.reportGroupCreationRequest.cityReportGroups[this.reportGroupCreationRequest.cityReportGroups.length - 1].name = this.myControl.value;
Promise.resolve(null).then(() => this.reportGroupCreationRequest.cityReportGroups.push(new CityCreationRequestModel()));
}
<div *ngFor="let city of reportGroupCreationRequest.cityReportGroups" class="form-row">
<mat-form-field>
<input placeholder="Stad" required type="text" matInput [formControl]="myControl" [matAutocomplete]="auto" name="cityInput">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let cityName of filteredOptions | async" [value]="cityName">
{{ cityName }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>

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

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.

Angular 6 show dropdown dependent selected option

I have two dropdown select option, but I'm struggling update second's selected option based on first one.
HTML
<form novalidate [formGroup]="editModuleForm" (ngSubmit)="onSubmitForm()">
<div align="center">
<mat-form-field>
<mat-select placeholder="Select Module" formControlName="moduleControl" required>
<mat-option *ngFor="let module of modules" [value]="module" (click)="popData()">
{{ module.title }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="editModuleForm.get('moduleControl').value" align="center">
<div align="center">
<mat-form-field>
<mat-select placeholder="Select Course" formControlName="courseControl">
<mat-option *ngFor="let course of courses" [value]="course.courseId" >
{{ course.name }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
Component
constructor(private putService: PutService, private getService: GetService,
private router: Router, private formBuilder: FormBuilder) {}
ngOnInit() {
this.getService.findAllModule().subscribe(modules => {
this.modules = modules;
});
this.getService.findAllCourses().subscribe(courses => {
this.courses = courses;
});
this.editModuleForm = this.formBuilder.group({
moduleControl: this.formBuilder.control(null),
courseControl: this.formBuilder.control(null)
});}
popData() {
this.editModuleForm.get('moduleControl').valueChanges.subscribe(
value => {
this.editModuleForm.controls['courseControl'].setValue(value.course);
console.log(value);
}
);}
After selecting an item in the first dropdown, I would like to default select module's course in the second dropdown and refresh after selecting another one.
You need subscribe changes on ngOnInit() no need to call click event
here's an example
ngOnInit() {
this.editModuleForm = this.formBuilder.group({
moduleControl: '',
courseControl: ''
});
this.popData();
}
popData() {
this.editModuleForm.controls['moduleControl'].valueChanges.subscribe(
value => {
this.editModuleForm.controls['courseControl'].setValue(value.id);
}
);
}
Stackblitz demo
I think the problem is you are calling this.editModuleForm.get('moduleControl').valueChanges in the click event, you actually want to call that in the ngOnInit. Its starting to listen to the value changes are the value has already changed!
Maybe try this:
constructor(private putService: PutService, private getService: GetService,
private router: Router, private formBuilder: FormBuilder) {}
ngOnInit() {
this.getService.findAllModule().subscribe(modules => {
this.modules = modules;
});
this.getService.findAllCourses().subscribe(courses => {
this.courses = courses;
});
this.editModuleForm = this.formBuilder.group({
moduleControl: this.formBuilder.control(null),
courseControl: this.formBuilder.control(null)
});
this.editModuleForm.get('moduleControl').valueChanges.subscribe(
value => {
this.editModuleForm.controls['courseControl'].setValue(value.course);
console.log(value);
}
}
You can get rid of the click event and the pop() method