Angular 6 show dropdown dependent selected option - html

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

Related

Update method passes the old value instead of the new value in Angular

I am trying to make an Update method and everything works fine except for a value that is selected from a drop-down-list. When the PUT method is called, the API always receives the old value instead of the newly selected one.
This is the list I am talking about:
edituser.component.html
<mat-form-field>
<mat-label>Choose a role </mat-label>
<mat-select *ngIf="roles" [(ngModel)]="userRoleID" name="role">
<mat-option *ngFor="let role of roles" [value]="role.id" [(ngModel)]="el.userRoleID">{{role.role}}</mat-option>
</mat-select>
</mat-form-field>
edituser.component.ts
export class EdituserComponent implements OnInit {
id: string;
sub: any;
user: User[];
roles: Role[];
userRoleID: string;
constructor(private route: ActivatedRoute, private userService: UserService, private router:Router, private roleService:RoleService) {
this.roleService.getRoles().subscribe((result) => {
this.roles = result
this.useRoles(this.roles);
});
}
ngOnInit(): void {
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
console.log(this.id);
this.userService.getUserById(this.id).subscribe((result) => this.user = result);
});
}
updateUser(user: User){
this.userService.updateUser(user).subscribe(
(result: User) => {
this.userService.getUsers();
});
this.router.navigateByUrl("");
}
useRoles(roles:any)
{
console.log(roles);
}
}
Why does it keep passing the wrong, outdated value to the API when trying to update?

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>

How to make tab key as enter key in Angular Material?

this is my angular materiel auto complete code
<input type="search" id="setId" name="setId" [attr.list]='collectionType' [(ngModel)]="selValue" class="text-box"
placeholder="--Select--" (focus)="ValidateParent()" (keyup.tab)="test()" (keyup)="EmitValues($event)" [id]="setId"
[matAutocomplete]="auto" [title]="selValue" [placeholder]='WaterMarkText'>
<div [hidden]="IsCascading">
<mat-autocomplete [id]="collectionType" #auto="matAutocomplete" (optionSelected)='onChange($event)'>
<mat-option *ngFor="let items of codeList" [value]="items.text" [attr.data-text]='items.text' [id]="items.value">
{{items.text}}
</mat-option>
</mat-autocomplete>
</div>
Angular material had a problem with tab selection.like the materiel auto complete not able to select the value while click the tab button. but it's working while click the enter button. So manually I need to overwrite the enter key event on tab key event. How could possible?
Improve my comment, and based on the response we can create a directive
import {
Directive,
AfterViewInit,
OnDestroy,
Optional
} from '#angular/core';
import {
MatAutocompleteTrigger
} from '#angular/material';
#Directive({
selector: '[tab-directive]'
})
export class TabDirective implements AfterViewInit, OnDestroy {
observable: any;
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger) {}
ngAfterViewInit() {
this.observable = this.autoTrigger.panelClosingActions.subscribe(x => {
if (this.autoTrigger.activeOption) {
this.autoTrigger.writeValue(this.autoTrigger.activeOption.value)
}
})
}
ngOnDestroy() {
this.observable.unsubscribe();
}
}
You use:
<input tab-directive type="text" matInput [formControl]="myControl"
[matAutocomplete]="auto" >
(see stackblitz)
Update We can control only tab.key, else always you close, you get the selected value, so
#Directive({
selector: '[tab-directive]'
})
export class TabDirective {
observable: any;
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger) {}
#HostListener('keydown.tab', ['$event.target']) onBlur() {
if (this.autoTrigger.activeOption) {
this.autoTrigger.writeValue(this.autoTrigger.activeOption.value)
}
}
}
(see a new stackblitz)
Update 2 I don't believe this answer has so many upvotes because it's wrong. As #Andrew allen comments, the directive not update the control. Well, It's late, but I try to solve. One Option is use
this.autoTrigger._onChange(this.autoTrigger.activeOption.value)
Anohter idea is inject the ngControl, so
constructor(#Optional() private autoTrigger: MatAutocompleteTrigger,
#Optional() private control: NgControl) {}
ngAfterViewInit() {
this.observable = this.autoTrigger.panelClosingActions.subscribe(x => {
if (this.autoTrigger.activeOption) {
const value = this.autoTrigger.activeOption.value;
if (this.control)
this.control.control.setValue(value, {
emit: false
});
this.autoTrigger.writeValue(value);
}
})
}
I'm a little late to the party; so there's one annoying issue with Eliseo's answer: It autocompletes even when the user has already selected a different option from the panel with a mouse click. The workaround I finally came up with was not straight forward at all.
/**
* Selects currently active item on TAB
* #example
* <input vxTabActiveOption [matAutocomplete]="matAutocomplate">
* <mat-autocomplete #matAutocomplate="matAutocomplete" autoActiveFirstOption>
*/
#Directive({ selector: '[vxTabActiveOption]' })
export class TabActiveOptionDirective implements AfterViewInit, OnDestroy {
private readonly destroy$ = new Subject<void>();
/**
* Whether or not the autocomplete panel was open before the event
*/
private panelOpen = false;
constructor(
private autoTrigger: MatAutocompleteTrigger,
private control: NgControl
) { }
ngAfterViewInit() {
const autocomplete = this.autoTrigger.autocomplete;
merge(
autocomplete.opened.pipe(map(() => true)),
autocomplete.closed.pipe(map(() => false))
).pipe(
takeUntil(this.destroy$),
delay(0)
).subscribe(value => this.panelOpen = value);
}
#HostListener('keydown.tab')
onBlur() {
if (this.panelOpen && this.autoTrigger.activeOption) {
const value = this.autoTrigger.activeOption.value;
this.control.control.setValue(value, { emit: false });
this.autoTrigger.writeValue(value);
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
Yes, I know it's an old question, but there're a better aproach that is use a directive like
#Directive({ selector: '[tab-selected]' })
export class TabSelected implements AfterViewInit {
constructor(private auto: MatAutocomplete) {}
ngAfterViewInit() {
this.auto._keyManager.onKeydown = (event: KeyboardEvent) => {
switch (event.keyCode) {
case TAB:
if (this.auto.isOpen) {
const option = this.auto.options.find(x => x.active);
if (option) {
option.select();
event.preventDefault();
return;
}
}
this.auto._keyManager.tabOut.next();
break;
case DOWN_ARROW:
this.auto._keyManager.setNextItemActive();
break;
case UP_ARROW:
this.auto._keyManager.setPreviousItemActive();
break;
}
};
}
}
You use like
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Number</mat-label>
<input type="text"
placeholder="Pick one"
aria-label="Number"
matInput
[formControl]="myControl"
[matAutocomplete]="auto">
<mat-autocomplete tab-selected #auto="matAutocomplete"
autoActiveFirstOption >
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
You can be on work in this stackblitz

Binding with ngModel angular dialog

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));
}
}