How to set auto focus in mat-select? - javascript

In my angular project have angular material and use mat-select. Mat-select is the first element for my form in my case set auto focus while page was loaded successfully but I wasn't able to set auto focus on mat-select. Anyone can help me to find the way to set auto focus in mat-select.
#ViewChild("name") nameField: ElementRef;
ngOninit() {
this.nameField.nativeElement.focus();
}
html
<div>
<mat-select [(ngModel)]="nameField" #name>
<mat-option *ngFor="let option of options2" [value]="option.id">
{{ option.name }}
</mat-option>
</mat-select>
</div>

HTML :
<mat-select #someRef >
<mat-option *ngFor="let item of items;" [value]="item">
{{item.name}}
</mat-option>
</mat-select>
.ts :
make sure you import MatSelect
import { MatSelect } from '#angular/material';
#ViewChild('someRef') someRef: MatSelect;
ngOnInit() {
if(this.someRef) this.someRef.focus();
}
Hope this helps.

If I understand it correctly, you want to focus select element on load. If this is the case, your code is perfectly fine, you just need to move focus logic in to another life cycle event which is
ngAfterViewInit
HTML:
<mat-select #fff>
<mat-option *ngFor="let food of foods" [value]="food.value">
{{food.viewValue}}
</mat-option>
</mat-select>
TS:
export class SelectOverviewExample implements AfterViewInit{
foods: Food[] = [
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos'}
];
#ViewChild("fff", {static: false}) nameField: ElementRef;
ngAfterViewInit() {
this.nameField.focused = true;
}
}
Find working demo here. You can see select is highlighted. comment code inside ngAfterViewInit() and see this difference.

As this is the First hit that shows up on Google I'll provide what I found:
Note that I did this specifically for a mat-select as there is no real inner html element that the reference could be attached to.
What I found works is getting a reference to the element through view-child and then calling
reference._elementRef.nativeElement.focus();
Hope this helps at least someone :)

We can use default angular attribute for autofocus
<mat-form-field>
<mat-select formControlName="xyz" cdkFocusInitial>
<mat-option value="abc">Abc</mat-option>
</mat-select>
</mat-form-field>

Try using MatSelect on viewChild to access focused attribute, then onInit set it to true.
<mat-form-field>
<mat-select #mySelect [(ngModel)]="nameField">
<mat-option *ngFor="let option of options2" [value]="option.id">{{ option.name }}
</mat-option>
</mat-select>
</mat-form-field>
and ts file import import { MatSelect } from '#angular/material';
import { MatSelect } from '#angular/material';
export class SelectExample implements OnInit {
#ViewChild(MatSelect) mySelect: MatSelect;
ngOnInit() {
this.mySelect.focused = true;
}
}

You can call the focus on OnInit
ts:
options2 = ['A', 'B'];
#ViewChild('name')
nameField: MdSelect;
ngOnInit() {
setTimeout(() => {
this.nameField.open();
}, 0);
}
html:
<div>
<md-select [(ngModel)]="nameField" #name>
<md-option *ngFor="let option of options2" [value]="option.id">{{ option }}</md-option>
</md-select>
EDIT: Sorry, I think you can not get the nativeElement from mat-select and md-select. You need to get the object and call open().
Workning project here in stackblitz

First, let’s create the Directive
auto-focus.directive.ts
import { AfterContentInit, Directive, ElementRef, Input } from '#angular/core';
#Directive({
selector: '[autoFocus]' }) export class AutofocusDirective implements AfterContentInit {
public constructor(private el: ElementRef) {
}
public ngAfterContentInit() {
setTimeout(() => {
this.el.nativeElement.focus();
}, 500);
}
}
Next we need to tell our AppModule that this new directive exists and to declare it for availability by updating our app.module.ts :
#NgModule({
declarations: [
AutoFocusDirective
]
})
Now you can use it in a component template:
app.component.html
<div> Autofocus? <input appAutoFocus> </div>

You can adapt this example to your own project. Clicking on the button becomes focus.
focusing on form elements the Angular way
show more

Related

how to close mat select when click outside

By default it should show the value if a user click the cell it will show the input field , if the user click outside will close the input field and will show the field value.
My issue is how do we made this possible to mat select and mat date picker . Any idea guys ? Thanks.
#blitz
https://stackblitz.com/edit/am-all-imports-4r6aik?file=app%2Fapp.component.html
#Ts
edit(index: number, column: string) {
this.editableColumn = column;
this.editableIndex = index;
}
showInput(index: number, column: string) {
return this.editableColumn === column && this.editableIndex === index;
}
showValue(index: number, column: string) {
return this.editableColumn !== column || this.editableIndex !== index;
}
Updated answer
use a custom directive to make the focus happen! then the solution will work!
directive
import { ElementRef } from '#angular/core';
import { Directive } from '#angular/core';
#Directive({
selector: '[appFocusOnLoad]',
})
export class FocusOnLoadDirective {
constructor(private elementRef: ElementRef) {}
ngOnInit() {
if (this.elementRef) {
this.elementRef.nativeElement.focus();
}
}
}
forked stackblitz
Below is an example of using blur for select and datepicker, select works great but for datepicker, you need to combine dateChange and blur to get the blur event during select and focus out!
html
<mat-form-field appearance="fill">
<mat-label>Input & change events</mat-label>
<input
matInput
[matDatepicker]="picker"
(blur)="blur()"
(dateChange)="blur()"
/>
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<mat-form-field>
<mat-select (blur)="blur()" placeholder="Fav Animal">
<mat-option *ngFor="let option of options" [value]="option">
{{ option }}
</mat-option>
</mat-select>
</mat-form-field>
<!-- Copyright 2022 Google LLC. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at https://angular.io/license -->
ts
import { Component } from '#angular/core';
import { FormControl } from '#angular/forms';
/** #title Basic datepicker */
#Component({
selector: 'datepicker-overview-example',
templateUrl: 'datepicker-overview-example.html',
})
export class DatepickerOverviewExample {
constructor() {}
options = ['Cat', 'Dog', 'Lion'];
selectedCountry: string = 'GB';
selectedCountryControl = new FormControl(this.selectedCountry);
blur() {
console.log('blurred');
}
}
/** Copyright 2022 Google LLC. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at https://angular.io/license */
forked stackblitz

Angular ng-content not working with mat-form-field

My goal:
I'm trying to build a reusable mat-form-field with a clear button.
How I tried achieving my goal:
I created a "mat-clearable-input" component and used it like this:
<mat-clearable-input>
<mat-label>Put a Number here pls</mat-label>
<input matInput formControlName="number_form_control">
</mat-clearable-input>
mat-clearable-input.component.html
<mat-form-field>
<ng-content></ng-content>
</mat-form-field>
Expected result:
the ng-content tag takes the label and the input and puts them inside the mat-form-field tag.
Actual result:
Error: mat-form-field must contain a MatFormFieldControl.
at getMatFormFieldMissingControlError (form-field.js:226)
at MatFormField._validateControlChild (form-field.js:688)
at MatFormField.ngAfterContentChecked (form-field.js:558)
at callHook (core.js:2926)
at callHooks (core.js:2892)
at executeInitAndCheckHooks (core.js:2844)
at refreshView (core.js:7239)
at refreshComponent (core.js:8335)
at refreshChildComponents (core.js:6991)
at refreshView (core.js:7248)
It looks like I'm missing something and I'm not using correctly the ng-content tag.
I wasn't able to locate the documentation for the ng-content tag on the angular website.
Thank you for any help.
EDIT AFTER ANSWER BELOW
So I tried this suggested method:
export class MatClearableInputComponent implements OnInit {
#ContentChild(MatFormFieldControl) _control: MatFormFieldControl<any>;
#ViewChild(MatFormField) _matFormField: MatFormField;
// see https://stackoverflow.com/questions/63898533/angular-ng-content-not-working-with-mat-form-field/
ngOnInit() {
this._matFormField._control = this._control;
}
}
unfortunately, when I try to use this in a form it still fails with the error "Error: mat-form-field must contain a MatFormFieldControl."
Code where i try to use this component in a form:
<mat-clearable-input>
<mat-label>Numero incarico</mat-label>
<buffered-input matInput formControlName="numero"></buffered-input>
</mat-clearable-input>
Repro on stackblitz: https://stackblitz.com/edit/angular-material-starter-xypjc5?file=app/clearable-form-field/clearable-form-field.component.html
notice how the mat-form-field features aren't working (no outline, no floating label), also open the console and you'll see the error Error: mat-form-field must contain a MatFormFieldControl.
EDIT AFTER OPTION 2 WAS POSTED
I tried doing this:
<mat-form-field>
<input matInput hidden>
<ng-content></ng-content>
</mat-form-field>
It works, but then when i added a mat-label to my form field, like this:
<mat-clearable-input>
<mat-label>Numero incarico</mat-label>
<buffered-input matInput formControlName="numero"></buffered-input>
</mat-clearable-input>
the label is never floating and it's just staying there as a normal span the whole time.
So i tried assigning to the this._matFormField._control._label the content child with the label but that didn't work because _label is private and there is no setter for it.
It looks like I'm out of luck and this can't be done in Angular without going through a lot of effort.
If you have any further ideas feel free to fork the stackblitz and try!
Edit after #evilstiefel answer
the solution works only for native <input matInput>.
When I try replacing that with my custom input component, it doesn't work anymore.
Working setup:
<mat-form-field appClearable>
<mat-label>ID incarico</mat-label>
<input matInput formControlName="id">
</mat-form-field>
Same setup but with my custom "buffered-input" component (not working :( )
<mat-form-field appClearable>
<mat-label>ID incarico</mat-label>
<buffered-input matInput formControlName="id"></buffered-input>
</mat-form-field>
The console logs this error when I click on the clear button:
TypeError: Cannot read property 'ngControl' of undefined
at ClearableDirective.clear (clearable.directive.ts:33)
at ClearButtonComponent.clearHost (clearable.directive.ts:55)
at ClearButtonComponent_Template_button_click_0_listener (clearable.directive.ts:47)
at executeListenerWithErrorHandling (core.js:14293)
at wrapListenerIn_markDirtyAndPreventDefault (core.js:14328)
at HTMLButtonElement.<anonymous> (platform-browser.js:582)
at ZoneDelegate.invokeTask (zone-evergreen.js:399)
at Object.onInvokeTask (core.js:27126)
at ZoneDelegate.invokeTask (zone-evergreen.js:398)
at Zone.runTask (zone-evergreen.js:167)
Another solution is using a directive to implement the behaviour.
import {
AfterViewInit,
Component,
ComponentFactory,
ComponentFactoryResolver,
ContentChild,
Directive,
Injector,
Input,
Optional,
SkipSelf,
TemplateRef,
ViewContainerRef,
} from '#angular/core';
import { MatFormFieldControl } from '#angular/material/form-field';
#Directive({
selector: '[appClearable]'
})
export class ClearableDirective implements AfterViewInit {
#ContentChild(MatFormFieldControl) matInput: MatFormFieldControl<any>;
#Input() appClearable: TemplateRef<any>;
private factory: ComponentFactory<ClearButtonComponent>;
constructor(
private vcr: ViewContainerRef,
resolver: ComponentFactoryResolver,
private injector: Injector,
) {
this.factory = resolver.resolveComponentFactory(ClearButtonComponent);
}
ngAfterViewInit(): void {
if (this.appClearable) {
this.vcr.createEmbeddedView(this.appClearable);
} else {
this.vcr.createComponent(this.factory, undefined, this.injector);
}
}
/**
* This is used to clear the formControl oder HTMLInputElement
*/
clear(): void {
if (this.matInput.ngControl) {
this.matInput.ngControl.control.reset();
} else {
this.matInput.value = '';
}
}
}
/**
* This is the markup/component for the clear-button that is shown to the user.
*/
#Component({
selector: 'app-clear-button',
template: `
<button (click)="clearHost()">Clear</button>
`
})
export class ClearButtonComponent {
constructor(#Optional() #SkipSelf() private clearDirective: ClearableDirective) { }
clearHost(): void {
if (this.clearDirective) {
this.clearDirective.clear();
}
}
}
This creates a directive called appClearable and an optional Component for a fallback-layout. Make sure to add the component and the directive to the declarations-array of your module. You can either specify a template to use for providing the user-interface or just use the ClearButtonComponent as a one-size-fits-all solution. The markup looks like this:
<!-- Use it with a template reference -->
<mat-form-field [appClearable]="clearableTmpl">
<input type="text" matInput [formControl]="exampleInput">
</mat-form-field>
<!-- use it without a template reference -->
<mat-form-field appClearable>
<input type="text" matInput [formControl]="exampleInput2">
</mat-form-field>
<ng-template #clearableTmpl>
<button (click)="exampleInput.reset()">Marked-Up reference template</button>
</ng-template>
This works with and without a ngControl/FormControl, but you might need to adjust it to your use-case.
Update:
Option 1 does not work for new angular versions because #ViewChild() returns undefined in ngOnInit() hook. Another hack is to use a dummy MatFormFieldControl -
Option 2
<mat-form-field>
<input matInput hidden>
<ng-content></ng-content>
</mat-form-field>
Edit:
That error is thrown because MatFormField component queries the child content using #ContentChild(MatFormFieldControl) which does not work if you use nested ng-content (MatFormField also uses content projection).
Option 1 (deprecated)
Below is how you can make it work -
#Component({
selector: 'mat-clearable-input',
template: `
<mat-form-field>
<ng-content></ng-content>
</mat-form-field>
`
})
export class FieldComponent implements OnInit {
#ContentChild(MatFormFieldControl) _control: MatFormFieldControl<any>;
#ViewChild(MatFormField) _matFormField: MatFormField;
ngOnInit() {
this._matFormField._control = this._control;
}
}
Please checkout this stackBlitz. Also, there is this issue created in github already.
As of Angular 14 in 2022, the issue for the MatFormField has been closed against angular/angular#37319, without a solution. If you need this to work, the following seems to be the best solution that's possible now to use <ng-content> with mat-form-field:
#Component({
selector: 'my-input-wrapper',
template: `
<mat-form-field appearance="standard">
<ng-content></ng-content>
<!-- make sure this is destroyed so all bindings/subscriptions are removed-->
<input *ngIf="isBeforeViewInit$$ | async" hidden matInput />
</mat-form-field>
`,
});
class MyInputWrapper implement AfterViewInit {
isBeforeViewInit$$ = new BehaviorSubject(true);
#ContentChild(MatFormFieldControl) matFormControl: MatFormFieldControl<unknown>;
#ViewChild(MatFormField) matFormField: MatFormField;
ngAfterViewInit() {
// replace the reference to the dummy control
this.matFormField._control = this.matFormControl;
// force the form field to rebind everything to the actual control
this.matFormField.ngAfterContentInit();
this.isBeforeViewInit$$.next(false);
}
}

How to create rows of mat-autocomplete elements in Angular app

I am building an angular app. In the template I have button, on clicking this it adds new autocomplete element. But problem arises when I select an option, I see other auto complete shows the previously filtered value. I filter method and form control is common for all the autocomplete elements. I am trying to figure out, how to make the formelement and filter method unique for each auto complete element.
Any help or suggestion will be really appreciated, thanks.
Here is the code snippet,
template.html
<button (click)="addelement()"> Add New Elemnt </button>
<ng-container *ngFor="let name of names; let j = index">
<form class="example-form">
<mat-form-field class="example-full-width">
<mat-label>Assignee</mat-label>
<input type="text" matInput [formControl]="myControl" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{option.name}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
</ng-container>
template.ts
import {Component, OnInit} from '#angular/core';
import {FormControl} from '#angular/forms';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
export interface User {
name: string;
}
/**
* #title Display value autocomplete
*/
#Component({
selector: 'autocomplete-display-example',
templateUrl: 'autocomplete-display-example.html',
styleUrls: ['autocomplete-display-example.css'],
})
export class AutocompleteDisplayExample implements OnInit {
myControl = new FormControl();
options: User[] = [
{name: 'Mary'},
{name: 'Shelley'},
{name: 'Igor'}
];
names=[{}]
filteredOptions: Observable<User[]>;
ngOnInit() {
this.filteredOptions = this.myControl.valueChanges
.pipe(
startWith(''),
map(value => typeof value === 'string' ? value : value.name),
map(name => name ? this._filter(name) : this.options.slice())
);
}
displayFn(user: User): string {
return user && user.name ? user.name : '';
}
private _filter(name: string): User[] {
const filterValue = name.toLowerCase();
return this.options.filter(option => option.name.toLowerCase().indexOf(filterValue) === 0);
}
addelement() {
this.names.push({});
}
}
In the code snippet above you will 'names' array, based on the object pushed to this array, autocomplete will appear in the template. If you need more info please let me know.

Error: Template parse errors: There is no directive with "exportAs" set to "matAutocomplete" (""auto" [formControl] ="..."

I am writing unit test to check if component is getting created successfully. I see the following error
Error: Template parse errors: There is no directive with "exportAs" set to "matAutocomplete" (""auto" [formControl
This is my template.html which contains auto-complete directive
<mat-form-field >
<input matInput [matAutocomplete]="auto" [formControl]="customerFilterControl">
<mat-autocomplete panelWidth ="450px" #auto="matAutocomplete" [displayWith] = "displayFn" style="width:750px;">
<mat-option *ngFor="let customer of filteredOptions | async" [value] ="customer.AccountID + '('+ customer.AccountName + ')'" (click)="onCustomerChange(customer)">
{{customer.AccountID}} ({{customer.AccountName}})
</mat-option>
</mat-autocomplete>
</mat-form-field>
This is the unit test spec.file, I have tried the following things,
import { ActualComponent } from './ActualComponent';
import { NO_ERRORS_SCHEMA,CUSTOM_ELEMENTS_SCHEMA,Directive } from '#angular/core';
#Directive({
selector:'<matAutocomplete>',
})
export class matAutocomplete{}
beforeEach(()=>{
TestBed.configureTestingModule({
imports:[....],
declarations:[...matAutocomplete],(1)
...
schemas:[NO_ERRORS_SCHEMA,CUSTOM_ELEMENTS_SCHEMA] //this didn't fix (2)
})
it('should create', () => {
expect(component).toBeTruthy();
})
I expected test to pass for (1) defining directive "matAutocomplete" and declaring in spec file (2) decalring schemas in testbed config But still test is not passing! Does anyone have suggestion for me?
The selector <matAutocomplete> will not work. Try to use [matAutoComplete] and add a #Input() matAutocomplete to the class. Next step would be to add exportAs to the directive decorator:
#Directive({
selector:'[matAutocomplete]',
exportAs: 'matAutocomplete'
})
export class matAutocomplete {
#Input() matAutocomplete: any;
}
UPDATE
May be it would be better to test this with the Angular Material Modules imported. Otherwise the test does not really test anything relevant.
Can you try the following:
1)
import {ReactiveFormsModule} from '#angular/forms';
import {MatAutocompleteModule} from '#angular/material/autocomplete';
import {MatInputModule} from '#angular/material/input';
import {MatSelectModule} from '#angular/material/select';
import {MatFormFieldModule} from '#angular/material/form-field';
...
// inside beforeEach:
TestBed.configureTestingModule({
imports:[ReactiveFormsModule,
MatAutocompleteModule, MatInputModule,
MatSelectModule, MatFormFieldModule],
declarations:[AppComponent], // add your component instead of AppComponent
// schemas:[NO_ERRORS_SCHEMA,CUSTOM_ELEMENTS_SCHEMA]
});
2) Remove the custom matAutocomplete directive.

How can I populate an array with selected values from dropdown in Angular?

I'm trying to populate an array with all selected values from a mat-select dropdown. The goal is to create an input field for every selected value like this:
Image
I thought about calling a method every time I select a value, but I'm not really sure on what to do next...
This is what I have:
MyComponent.component.html
<mat-form-field>
<mat-select placeholder="Products" [formControl]="products" multiple>
<mat-option *ngFor="let product of productsList">{{ product }}</mat-option>
</mat-select>
</mat-form-field>
MyComponent.component.ts
import { Component, OnInit } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'm-mycomponent',
templateUrl: './mycomponent.component.html',
styleUrls: ['./mycomponent.component.scss']
})
export class MyComponent implements OnInit {
products = new FormControl();
productsList = ['Prod1', 'Prod2', 'Prod3', 'Prod4', 'Prod5', 'Prod6'];
productsToReturn = [];
constructor() { }
ngOnInit() {
}
fillProductsToReturn(product){
if(!this.productsToReturn.includes(product)){
this.productsToReturn.push(product);
}
}
}
How can I call a method in the html file and populate the productsToReturn array?
Thanks!
You didn't specify when you want to access the selected objects.
Its simple if you want to access it when the form is submitted, like on a click of a button. Then you can use this.products.value to get the selected options.
If you want it at the time of selection, then you can bind to selectionChange() event
<mat-select placeholder="Products" [formControl]="products" multiple (selectionChange)="onSelectionChange($event) >
then in ts file you can get the selected options
onSelectionChange(e){
console.log(e.value); //or
console.log(this.toppings.value);
}
You don't need to do this manually. Just bind the value property of the mat-option to the current product string and your FormControl will contain the array of selected options.
Template
<mat-form-field>
<mat-select placeholder="Products" [formControl]="products" multiple>
<mat-option *ngFor="let product of productsList" [value]="product">{{ product }}</mat-option>
</mat-select>
</mat-form-field>
And in your component you can access the selected options via this.products.value.
Here
is a minimal stackblitz with a working solution. The selected values
are directly displayed next to the mat-select.
I solved my problem...
I made the following changes based on j4rey's answer
MyComponent.component.html
<mat-form-field>
<mat-select placeholder="Products" [formControl]="products" multiple>
<mat-option *ngFor="let product of productsList" [value]="product" (onSelectionChange)="onSelectionChange(product)">{{ product }}</mat-option>
</mat-select>
</mat-form-field>
<div class="row" *ngFor="let product of productsToReturn">
<div class="col-4">
<mat-form-field>
<input matInput disabled="disabled" [value]="product">
</mat-form-field>
</div>
<div class="col-2">
<mat-form-field class="prod-qty">
<input matInput step="1" value="1" min="1" type="number">
</mat-form-field>
</div>
</div>
MyComponent.component.ts
import { Component, OnInit } from '#angular/core';
import { FormControl } from '#angular/forms';
#Component({
selector: 'm-mycomponent',
templateUrl: './mycomponent.component.html',
styleUrls: ['./mycomponent.component.scss']
})
export class MyComponent implements OnInit {
products = new FormControl();
productsList = ['Prod1', 'Prod2', 'Prod3', 'Prod4', 'Prod5', 'Prod6'];
productsToReturn = [];
constructor() { }
ngOnInit() { }
onSelectionChange(product: string) {
if (!this.productsToReturn.includes(product)) {
this.productsToReturn.push(product);
} else {
let index = this.productsToReturn.indexOf(product);
this.productsToReturn.splice(index, 1);
}
}
}

Categories

Resources