Is it possible to show user entered text in <mat-select>? - javascript

<mat-select [(ngModel)]="textList" >
<mat-option *ngFor="let textof list" [value]="text">
{{ text }}
</mat-option>
<mat-form-field class="example-full-width">
<input matInput [(ngModel)]="holdReason"/>
</mat-form-field>
</mat-select>
using this code I can show a input field inside the mat-select. but if give some values to that input field that value is not appear in the box. it is empty. Is it possible to just show the text that we entered in input box show inside mat-select
This is what i can see after enter something in the input and just press enter

Here is a working version, you can refer the below stackblitz, do let me know if any doubts. You can enter a new value by giving enter key.
ts
import { ViewChild } from '#angular/core';
import { ElementRef } from '#angular/core';
import { Component } from '#angular/core';
import { MatSelect } from '#angular/material/select';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
#ViewChild('matSelect') matSelect: ElementRef<MatSelect> | MatSelect;
public variables = ['One', 'Two', 'County', 'Three', 'Zebra', 'XiOn'];
public variables2 = [
{ id: 0, name: 'One' },
{ id: 1, name: 'Two' },
];
selected = null;
public filteredList1 = this.variables.slice();
public filteredList2 = this.variables.slice();
public filteredList3 = this.variables.slice();
public filteredList4 = this.variables.slice();
public filteredList5 = this.variables2.slice();
enter(e) {
const matSelect: MatSelect =
this.matSelect && (<ElementRef<MatSelect>>this.matSelect).nativeElement
? <MatSelect>(<ElementRef<MatSelect>>this.matSelect).nativeElement
: <MatSelect>this.matSelect;
// console.log(this.matSelect);
const value = e.target && e.target.value;
if (value && matSelect) {
e.target.value = '';
this.variables.push(value);
this.filteredList1 = this.variables;
this.selected = value;
matSelect.close();
}
}
}
html
<div class="center">
<mat-form-field>
<mat-select placeholder="Basic" #matSelect [(ngModel)]="selected">
<mat-select-filter
[array]="variables"
(filteredReturn)="filteredList1 = $event"
(keydown.enter)="enter($event)"
></mat-select-filter>
<mat-option *ngFor="let item of filteredList1" [value]="item">
{{ item }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
forked stackblitz

Related

mat-autocomplete search works fine with mock data , wont work when assigning data from the backend service

mat-autocomplete works great with mock data. when i integrate it with the actual service , It won't populate the dropdown.
my html looks like this
<mat-form-field class="example-full-width">
<input
matInput
placeholder="LastName, FirstName"
aria-label="LastName, FirstName"
[matAutocomplete]="auto"
[formControl]="userCtrl"
/>
<mat-autocomplete #auto="matAutocomplete">
<mat-option
(click)="onSelect(user,i)"
*ngFor="let user of filteredUsers | async; let i = index"
[value]="user.name"
>
<span>{{ user.name }}</span>
<!-- <small> GUID: {{user.guid}}</small> -->
</mat-option>
</mat-autocomplete>
</mat-form-field>
component is like below
import {Component, Inject, ViewChild, OnInit} from '#angular/core';
import { MAT_DIALOG_DATA} from '#angular/material/dialog';
import { FormControl } from "#angular/forms";
import { Observable } from "rxjs";
import { map, startWith } from "rxjs/operators";
import { UserService } from '../../../shared/services/user/user.service';
#Component({
selector: "app-change-user-dialog",
templateUrl: "./change-user-dialog.component.html",
styleUrls: ["./change-user-dialog.component.scss"]
})
export class ChangeUserComponent implements OnInit {
userCtrl: FormControl;
filteredUsers: any;
selectedUser = "";
users = [
{
name: "Barrett, David",
guid: "1213123"
},
{
name: "Barrett, Elizabeth",
guid: "8437593"
},
{
name: "Barrett, Elizabeth",
guid: "2o934u124"
}
];
constructor( public dialogRef: MatDialogRef<ChangeUserDialogComponent>,
#Inject(MAT_DIALOG_DATA) public data: ChangeUserDialogComponent,
private userService: UserService ) {
this.userCtrl = new FormControl();
this.filteredUsers = this.userCtrl.valueChanges.pipe(
startWith(""),
map(user => (user.length >= 3 ? this.filterUsers(user) : []))
);
}
ngOnInit(): void {
}
filterUsers(name) {
this.users.filter((user) => {
return user.name.toLowerCase().indexOf(name.toLowerCase()) === 0
});
}
}
above component works fine and displays the dropdown with mock data ,
Now, Im trying to get the response back from service and assign it back to this.users and it won't work .
Below is what i tried
filterUsers(name: string) {
var split_str = name.split(",");
const lastname = split_str[0] ? split_str[0] : "";
const firstname = split_str[1] ? split_str[1] : "";
this.userService.getGuidByName(firstname, lastname).subscribe((data) => {
this.users = data;
return this.users.filter(
user => user.name.toLowerCase().indexOf(name.toLowerCase()) === 0
);
});
}
my service looks like below
public getGuidByName(firstname, lastname): Observable<any> {
return this.httpService.get('file', `/search?firstname=${firstname}&lastname=${lastname}`)
.pipe(
catchError(this.httpService.handleError('error'))
);
}
service response is exactly same as mock data that i am using.
what am i missing ? Any input is appreciated. Thanks.

Matselect default values based on radiobuttons

I have three matformfields named Form A,Form B,Form C and three mat radio buttons named A,B,C.
What I want is that when radiobutton A is enabled or checked Form A's default value should be A and in other two form fields there should be no value by default. When radiobutton B is enabled or checked Form B's default value should be B and in other two form fields there should be no value by default.I want the same for radiobutton C. I am getting the dropdown data from service.
Sample data:
listes: any[] = [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 3, name: "C" },];
WHAT I HAVE TRIED:
I tried to get the id 1 which has value A and used setvalue to patch it in Form A but its not worling
const toSelect = this.listes.find(c => c.id == 1);
this.patientCategory.get('patientCategory').setValue(toSelect);
STACKBLITZ: https://stackblitz.com/edit/how-to-set-default-value-of-mat-select-when-options-are-reo3xn?file=app%2Ftable-basic-example.html
I've corrected your code as per the scenario you described. Although my temporary code is a little lengthy, it applies the logic you described. But I hope you simplify it further.
Fix:
[value] attribute of a mat-option shouldn't be set to category itself as category is an object. [value] expects a singular uniquely identifying value. So you should set it to [value] = "category.name". Ideally, we set value to unique identifiers like [value]="category.id" or [value]="category.key" etc.
The three mat-selects should behave independently in your scneario. So they should never be assigned to the same formControl. Instead, assign individual formControls to have full control over them.
Finally, you can use the function valueChange assigned to the radio buttons, to conditionally apply values in FormA, FormB and FormC as per your scenario.
<form [formGroup]="patientCategory">
<mat-form-field class="full-width">
<mat-select placeholder="FORMA" formControlName="patientCategoryA">
<mat-option *ngFor="let category of listes" [value]="category.name">
{{category.name}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="full-width">
<mat-select placeholder="FORMB" formControlName="patientCategoryB">
<mat-option *ngFor="let category of listes" [value]="category.name">
{{category.name}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="full-width">
<mat-select placeholder="FORMC" formControlName="patientCategoryC">
<mat-option *ngFor="let category of listes" [value]="category.name">
{{category.name}}
</mat-option>
</mat-select>
</mat-form-field>
</form>
<div class="container" style="margin-top: 0px;">
<div class="example-container">
<mat-radio-group #group="matRadioGroup" [(ngModel)]="test" [(value)]="selectedValue"
(change)="onValueChange(group.value)">
<mat-radio-button value="A" [checked]="defaultSelected">A</mat-radio-button>
<mat-radio-button style="margin-left:10px" value="B">B</mat-radio-button>
<mat-radio-button style="margin-left:10px" value="C">C</mat-radio-button>
</mat-radio-group>
</div>
</div>
import { Component, ViewChild } from "#angular/core";
import {
FormBuilder,
FormGroup,
FormControl,
Validators
} from "#angular/forms";
import { DataService } from "./data.service";
/**
* #title Basic table
*/
#Component({
selector: "table-basic-example",
styleUrls: ["table-basic-example.css"],
templateUrl: "table-basic-example.html"
})
export class TableBasicExample {
patientCategory: FormGroup;
listes: any[];
constructor(private fb: FormBuilder, private dataService: DataService) {}
ngOnInit() {
this.dataService.getData().subscribe(res => {
this.listes = res;
});
this.patientCategory = this.fb.group({
patientCategoryA: [null, Validators.required],
patientCategoryB: [null, Validators.required],
patientCategoryC: [null, Validators.required]
});
}
onValueChange(value) {
if (value === "A") {
this.patientCategory.get("patientCategoryA").setValue(value);
this.patientCategory.get("patientCategoryB").setValue(null);
this.patientCategory.get("patientCategoryC").setValue(null);
} else if (value === "B") {
this.patientCategory.get("patientCategoryB").setValue(value);
this.patientCategory.get("patientCategoryA").setValue(null);
this.patientCategory.get("patientCategoryC").setValue(null);
} else if (value === "C") {
this.patientCategory.get("patientCategoryC").setValue(value);
this.patientCategory.get("patientCategoryB").setValue(null);
this.patientCategory.get("patientCategoryA").setValue(null);
}
}
}
Hope this helps. Let me know if you have any issues.

Angular: mat-form-field must contain a MatFormFieldControl

I am trying to add a form field with custom telephone number input control. I used the example of the phone from https://material.angular.io/components/form-field/examples.
Here is the code:
<mat-form-field>
<example-tel-input placeholder="Phone number" required></example-tel-input>
<mat-icon matSuffix>phone</mat-icon>
<mat-hint>Include area code</mat-hint>
</mat-form-field>
import {FocusMonitor} from '#angular/cdk/a11y';
import {coerceBooleanProperty} from '#angular/cdk/coercion';
import {Component, ElementRef, Input, OnDestroy} from '#angular/core';
import {FormBuilder, FormGroup} from '#angular/forms';
import {MatFormFieldControl} from '#angular/material';
import {Subject} from 'rxjs';
/** #title Form field with custom telephone number input control. */
#Component({
selector: 'form-field-custom-control-example',
templateUrl: 'form-field-custom-control-example.html',
styleUrls: ['form-field-custom-control-example.css'],
})
export class FormFieldCustomControlExample {}
/** Data structure for holding telephone number. */
export class MyTel {
constructor(public area: string, public exchange: string, public subscriber: string) {}
}
/** Custom `MatFormFieldControl` for telephone number input. */
#Component({
selector: 'example-tel-input',
templateUrl: 'example-tel-input-example.html',
styleUrls: ['example-tel-input-example.css'],
providers: [{provide: MatFormFieldControl, useExisting: MyTelInput}],
host: {
'[class.example-floating]': 'shouldLabelFloat',
'[id]': 'id',
'[attr.aria-describedby]': 'describedBy',
}
})
export class MyTelInput implements MatFormFieldControl<MyTel>, OnDestroy {
static nextId = 0;
parts: FormGroup;
stateChanges = new Subject<void>();
focused = false;
ngControl = null;
errorState = false;
controlType = 'example-tel-input';
id = `example-tel-input-${MyTelInput.nextId++}`;
describedBy = '';
get empty() {
const {value: {area, exchange, subscriber}} = this.parts;
return !area && !exchange && !subscriber;
}
get shouldLabelFloat() { return this.focused || !this.empty; }
#Input()
get placeholder(): string { return this._placeholder; }
set placeholder(value: string) {
this._placeholder = value;
this.stateChanges.next();
}
private _placeholder: string;
#Input()
get required(): boolean { return this._required; }
set required(value: boolean) {
this._required = coerceBooleanProperty(value);
this.stateChanges.next();
}
private _required = false;
#Input()
get disabled(): boolean { return this._disabled; }
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
this.stateChanges.next();
}
private _disabled = false;
#Input()
get value(): MyTel | null {
const {value: {area, exchange, subscriber}} = this.parts;
if (area.length === 3 && exchange.length === 3 && subscriber.length === 4) {
return new MyTel(area, exchange, subscriber);
}
return null;
}
set value(tel: MyTel | null) {
const {area, exchange, subscriber} = tel || new MyTel('', '', '');
this.parts.setValue({area, exchange, subscriber});
this.stateChanges.next();
}
constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef<HTMLElement>) {
this.parts = fb.group({
area: '',
exchange: '',
subscriber: '',
});
fm.monitor(elRef, true).subscribe(origin => {
this.focused = !!origin;
this.stateChanges.next();
});
}
ngOnDestroy() {
this.stateChanges.complete();
this.fm.stopMonitoring(this.elRef);
}
setDescribedByIds(ids: string[]) {
this.describedBy = ids.join(' ');
}
onContainerClick(event: MouseEvent) {
if ((event.target as Element).tagName.toLowerCase() != 'input') {
this.elRef.nativeElement.querySelector('input')!.focus();
}
}
}
example-tel-input-example.html
<div [formGroup]="parts" class="example-tel-input-container">
<input class="example-tel-input-element" formControlName="area" size="3">
<span class="example-tel-input-spacer">–</span>
<input class="example-tel-input-element" formControlName="exchange" size="3">
<span class="example-tel-input-spacer">–</span>
<input class="example-tel-input-element" formControlName="subscriber" size="4">
</div>
But I get the following error:
ERROR Error: mat-form-field must contain a MatFormFieldControl.
Need to import two module and add those in imports and exports section.
import { MatFormFieldModule } from '#angular/material/form-field';
import { MatInputModule } from '#angular/material/input';
#NgModule ({
imports: [ MatFormFieldModule, MatInputModule ],
exports: [ MatFormFieldModule, MatInputModule ]
})
And the most thing which everybody miss this '/' character. if you see the Angular Material Documentation , they also miss this (Last Checked 16 Jun 2020, don't know they even updated or not). I make an example for clarifications
<!-- Wrong -->
<mat-form-field>
<input matInput>
</mat-form-field>
<!-- Right -->
<mat-form-field>
<input matInput />
</mat-form-field>
Look at the snippet carefully. when <input begin it must close with /> but most people miss the / (backslash) character.
Make sure MatInputModule is imported and <mat-form-field> contains <input> with matInput / matSelect directives.
https://github.com/angular/material2/issues/7898
Import MatInputModule, solved my error
You need to specify your class as a provider for MatFormFieldControl
https://material.angular.io/guide/creating-a-custom-form-field-control#providing-our-component-as-a-matformfieldcontrol
#Component({
selector: 'form-field-custom-control-example',
templateUrl: 'form-field-custom-control-example.html',
styleUrls: ['form-field-custom-control-example.css'],
providers: [
{ provide: MatFormFieldControl, useExisting: FormFieldCustomControlExample }
]
})
Also, don't forget to write the name attribute in the input tag:
name="yourName"
if you are using any 'input' tag in your code along with 'mat-form-field', make sure to include 'matInput' in the input tag
if there is any *ngIf present in child tag of 'mat-form-field', specify the *ngIf condition in 'mat-form-field' tag
Another possible issue closing the input tag. If you copy code from one of the Angular examples (https://material.angular.io/components/datepicker/overview), you may end up with the code:
<mat-form-field appearance="fill">
<mat-label>Choose a date</mat-label>
<input matInput [matDatepicker]="picker">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
The input should have the close tag (slash):
<input matInput [matDatepicker]="picker" />
This will fix your issue
import {
MatFormFieldModule,
MatInputModule
} from "#angular/material";
Error says that mat-form-field should contain at least one form field from which input is taken.
Ex : matInput mat-select etc.
In your case you may add matInput tag to your input fields within example-tel-input-example.html. And also you could move mat-form-field inside example-tel-input-example component and add it against each matInput field.
<div [formGroup]="parts" class="example-tel-input-container">
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="area" size="3">
</mat-form-field>
<span class="example-tel-input-spacer">–</span>
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="exchange" size="3">
</mat-form-field>
<span class="example-tel-input-spacer">–</span>
<mat-form-field>
<input matInput class="example-tel-input-element" formControlName="subscriber" size="4">
</mat-form-field>
</div>
Note : mat-icon or mat-hint cannot be considered as form-fields

How to check for state change in angular 4/6?

My task is to create an account information web page which consists of 4 pre-filled fields (given name, family name, username and email) and a common save button at the bottom. User can change any field by the respective field. I want save button to be enabled only if user changes any fields. Any method to track state changes? My code is as follows:
<mat-card-content>
<div class="form-group">
<mat-form-field class="simple-form-field-50">
<input matInput placeholder="Given name" name="givenName" formControlName="givenName">
</mat-form-field>
<mat-form-field class="simple-form-field-50">
<input matInput placeholder="Family name" name="familyName" formControlName="familyName">
</mat-form-field>
<br>
<mat-form-field>
<input matInput placeholder="Email" name="email" formControlName="email">
</mat-form-field>
<br>
<button
[disabled]="waiting"
class="simple-form-button"
color="primary"
mat-raised-button
type="submit"
value="submit">
Save
</button>
</div>
</mat-card-content>
My Code Output:
Since you're using a Reactive Form, you can use valueChanges on the FormGroup.
Since it is of type Observable, you can subscribe to it to set a variable of type boolean that will be used in the template to enable the button.
...
#Component({...})
export class AppComponent {
form: FormGroup;
disableButton = true;
ngOnInit() {
...
this.form.valueChanges.subscribe(changes => this.disableButton = false);
}
}
And in your template:
<form [formGroup]="form">
...
<button [disabled]="disableButton">Submit</button>
</form>
UPDATE:
If you want to disable it when values don't really change, check for the current value of the form with the previous value:
import { Component } from '#angular/core';
import { FormGroup, FormControl } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
form: FormGroup;
disableButton = true;
userValue = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe#domain.com'
}
ngOnInit() {
this.form = new FormGroup({
firstName: new FormControl(),
lastName: new FormControl(),
email: new FormControl()
});
this.form.patchValue(this.userValue);
this.form.valueChanges.subscribe(changes => this.wasFormChanged(changes));
}
private wasFormChanged(currentValue) {
const fields = ['firstName', 'lastName', 'email'];
for(let i = 0; i < fields.length; i++) {
const fieldName = fields[i];
if(this.userValue[fieldName] !== currentValue[fieldName]) {
console.log('Came inside');
this.disableButton = false;
return;
}
}
this.disableButton = true;
}
}
NOTE: StackBlitz is updated accordingly.
Here's a Working Sample StackBlitz for your ref.
onChange(targetValue : string ){
console.log(targetValue );}
<input matInput placeholder="test" name="test" formControlName="testNM" (input)="onChange($event.target.value)">
This is called Dirty Check.
You may find this SO answer very useful:
https://stackoverflow.com/a/50387044/1331040
Here is the guide for Template-Driven Forms
https://angular.io/guide/forms
Here is the guide for Reactive Forms
https://angular.io/guide/reactive-forms
And here is the difference between two concepts
https://blog.angular-university.io/introduction-to-angular-2-forms-template-driven-vs-model-driven/
Hope these help.
I would do something like this:
form: FormGroup;
disableButton = true;
originalObj: any;
ngOnInit() {
this.form = new FormGroup({
control: new FormControl('Value')
});
this.originalObj = this.form.controls['control'].value; // store the original value in one variable
this.form.valueChanges.subscribe(changes => {
if (this.originalObj == changes.control) // this if added for check the same value
{
this.disableButton = true;
}
else {
this.disableButton = false;
}
}
);
}
WORKING EXAMPLE

Unable to access the members which are instantiated in one method in another method in Angular

I am able to store the data of user.id in the displayFn() function into the sourceId and print it in the console, but when I am trying to access it in the onClick() even after the displayFn() is called it shows undefined in the console. Please don't mind if this is silly question. Below is my code for the component. Thanks in advance.
import { Component, OnInit } from '#angular/core';
import { FormControl } from '#angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { DataService } from '../data.service';
import { Router } from '#angular/router'
import { Global } from './Global'
import { LocationsService } from '../locations.service';
export class Locations {
id: string;
value: string;
}
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
loc: Locations[] = [];
sourceId: number;
destinationId: number;
myControl1 = new FormControl();
myControl = new FormControl();
filteredOptions1: Observable<Locations[]>;
filteredOptions: Observable<Locations[]>;
constructor(private router: Router, private locations: LocationsService) {}
ngOnInit() {
this.locations.getLocations().subscribe(
data => {
for (var i = 0; data[i] != null; i++) {
this.loc.push(data[i]);
}
},
error => {
console.log("error in receiving data");
},
);
this.filteredOptions = this.myControl.valueChanges
.pipe(
startWith<string | Locations>(''),
map(value => typeof value === 'string' ? value : value.value),
map(name => name ? this._filter(name) : this.loc.slice())
);
this.filteredOptions1 = this.myControl1.valueChanges
.pipe(
startWith<string | Locations>(''),
map(value => typeof value === 'string' ? value : value.value),
map(name => name ? this._filter1(name) : this.loc.slice())
);
}
displayFn(user: Locations): string {
this.sourceId = parseInt(user.id);
console.log("sourceId = " + this.sourceId);
return user.value;
}
private _filter(name: string): Locations[] {
const filterValue = name.toLowerCase();
return this.loc.filter(option => option.value.toLowerCase().indexOf(filterValue) === 0);
}
public displayFn1(user: Locations): string {
this.destinationId = parseInt(user.id);
console.log("destinationId = " + this.destinationId);
return user.value;
}
private _filter1(name1: string): Locations[] {
const filterValue1 = name1.toLowerCase();
return this.loc.filter(option1 => option1.value.toLowerCase().indexOf(filterValue1) === 0);
}
onClick() {
console.log("from onclick, sourceID = " + this.sourceId);
console.log("from onclick, sourceID = " + this.destinationId);
this.router.navigate(['/services', this.sourceId,this. destinationId]);
}
}
Below is my HTML which uses Angular material Autocomplete Form.
<form>
<input type="text" placeholder="To" matInput [formControl]="myControl" [matAutocomplete]="auto" id="toPlaceName"
class="ajxPlaceCs ui-autocomplete-input">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option.value}}
</mat-option>
</mat-autocomplete>
<input type="text" placeholder="From" matInput [formControl]="myControl1" [matAutocomplete]="auto1" id="fromPlaceName"
class="ajxPlaceCs ui-autocomplete-input">
<mat-autocomplete #auto1="matAutocomplete" [displayWith]="displayFn1">
<mat-option *ngFor="let option1 of filteredOptions1 | async" [value]="option1">
{{option1.value}}
</mat-option>
</mat-autocomplete>
<input type="button" name="searchBtn" (click)="onClick()" id="searchBtn" class="chkavailabilityBtn"
value="Check Availability" title="Search">
</form>
Try updating your [displayWith]="displayFn" to [displayWith]="displayFn.bind(this)".
You can check here for more information.

Categories