在我们的应用程序中有许多mat-form-field
元素。其中部分包含MatSelect
下拉菜单,但大部分包含具有matInput
属性的input[type=text]
元素。
我有一个指令ClearButtonDirective
与非常一般的selector: 'mat-form-field'
-这是有意的,所以它修改了所有的mat-form-field
s。指令插入了一个"清除"按钮。到所有mat-form-field
元素中,当单击时清除输入元素的值,它只在文本字段中有一些值时出现。
我有两个问题,这两者都是有关的事实,我不知道如何找到输入元素与@ViewChild
或@ContentChild
装饰器(说实话,我还不确定这两者之间的区别是什么)。
- 在
onClearClick()
中,值被清除(使用MatFormFieldControl
子),但是我还想关注输入字段之后——怎么做?我需要它的参考资料… - 我只想要指令对那些包含
input[matInput]
的mat-form-field
s采取行动(插入清除按钮),但不是其他,例如那些包含mat-select
-如何?再次,为此我需要能够检测/找到input[matInput]
子。
我不想修改现有的模板来实现这一点,所以我不能添加任何元素引用或CSS类。
StackBlitz的工作示例—前三个盒子是mat-form-field
s和matInput
,第四个盒子是mat-select
。
谢谢!❤
clear-button.directive.ts
:
import { AfterViewInit, Component, ComponentFactoryResolver, ComponentRef, ContentChild, Directive, ElementRef, EventEmitter, Output, Renderer2, ViewContainerRef } from '@angular/core';
import { MatFormFieldControl } from '@angular/material/form-field';
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
@Component({
selector: 'button.clear-button',
template: 'Clear',
host: {
'[hidden]': 'hidden',
'(click)': 'onClick()',
}
})
export class ClearButtonComponent {
@Output() clearButtonClick = new EventEmitter<void>();
hidden = true;
onClick() {
this.clearButtonClick.next();
}
}
@Directive({
selector: 'mat-form-field'
})
export class ClearButtonDirective implements AfterViewInit {
private buttonComponent: ComponentRef<ClearButtonComponent>;
@ContentChild(MatFormFieldControl) control: MatFormFieldControl<any>;
constructor(
private _viewContainer: ViewContainerRef,
private _resolver: ComponentFactoryResolver,
private _hostElement: ElementRef,
private _renderer: Renderer2,
) {}
ngAfterViewInit() {
const buttonFactory = this._resolver.resolveComponentFactory(ClearButtonComponent);
this.buttonComponent = this._viewContainer.createComponent(buttonFactory);
this._renderer.appendChild(this._hostElement.nativeElement, this.buttonComponent.location.nativeElement);
// Listen to button clicks:
this.buttonComponent.instance.clearButtonClick.subscribe(this.onClearClick.bind(this));
this.control.stateChanges.pipe(
startWith(true),
map(_ => !!this.control.value),
distinctUntilChanged(),
).subscribe(shouldHideButton => this.buttonComponent.instance.hidden = !shouldHideButton);
}
onClearClick() {
this.control.value = '';
}
}
app.component.html
:
<form [formGroup]="form">
<!-- clear button should appear here - works fine -->
<mat-form-field>
<mat-label>First name - matInput</mat-label>
<input matInput type="text" formControlName="firstName" />
</mat-form-field>
<!-- clear button should appear here - works fine -->
<mat-form-field>
<mat-label>First name - matInput</mat-label>
<input matInput type="text" formControlName="lastName" />
</mat-form-field>
<!-- clear button should appear here - works fine -->
<mat-form-field>
<mat-label>Type anything - matInput</mat-label>
<input matInput type="text" formControlName="someField" />
</mat-form-field>
<!-- clear button should ***NOT*** appear here - how to achieve that? -->
<mat-form-field>
<mat-label>MatSelect - clear button <b>NOT</b> wanted here</mat-label>
<mat-select formControlName="someSelect">
<mat-option value="one">First option</mat-option>
<mat-option value="two">Second option</mat-option>
</mat-select>
</mat-form-field>
</form>
app.component.ts
:
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
form: FormGroup;
ngOnInit() {
this.form = new FormGroup({
firstName: new FormControl('John'),
lastName: new FormControl('Doe'),
someField: new FormControl(),
someSelect: new FormControl('one'),
});
}
}
好问题!
在onClearClick()的值被清除(使用MatFormFieldControl子),但我也想关注输入字段之后-如何?我需要它的参考资料……
您已经引用了控件,只需要使用本地HTML元素
进行聚焦onClearClick() {
this.control.value = "";
((this.control as unknown) as HTMLElement).focus();
}
我只希望指令在那些包含输入[matInput]的垫子表单字段上起作用(插入清除按钮),但不是其他人,例如那些包含垫子选择的字段-如何?同样,为此我需要能够检测/找到input[matInput]子元素。
控件有一个可以检查其类型的属性controlType
ngAfterViewInit() {
if (this.control.controlType === "mat-input") {
...
}
}
但是,请实现ngAfterContentInit而不是ngAfterViewInit。
第一期:
所有input[text]元素将默认实现DefaultValueAccessor。因此,您可以检查类似这样的内容来动态添加btn。
if (this.control.ngControl.valueAccessor instanceof DefaultValueAccessor) {
const buttonFactory = this._resolver.resolveComponentFactory(
ClearButtonComponent
);
this.buttonComponent = this._viewContainer.createComponent(buttonFactory);
this._renderer.appendChild(
this._hostElement.nativeElement,
this.buttonComponent.location.nativeElement
);
}
问题2:
matInput指令为MatFormFieldControl类提供了matInput实例,因此我们可以在它上面调用focus方法来聚焦输入。
onClearClick() {
//Use setValue method on control to set empty value on control.Otherwise it will not propagate to parent formGroup.
this.control.ngControl.control.setValue('');
(this.control as MatInput).focus();
}
工作示例
我认为上述两种方法的结合似乎是有效的。
但是如果你想让它更有性能,请把它们组合在一起。
请确保您将始终遵循您将来将使用的每个装饰器的文档。正如T. Sunil Rao的评论所写的那样,最好依赖于ngAfterContentInit中ContentChild的回调。
我会把一个特定的函数放在整个逻辑之前,并遵循Guard子句模式
if (this.isNotEligible()) {
return;
}
// rest of the code here
/**
* Determine if you need to continue with logic
*/
isNotEligible(): boolean {
return this.control.controlType !== "mat-input";
}
没有必要做像instanceof
这样昂贵的操作。
如果你想为你的应用程序制造更少的问题,请避免存储过时的指针。
const buttonComponent = this._viewContainer.createComponent(buttonFactory);
没有必要将其作为引用存储在指令上下文中,一旦您通过ViewContainerRef
创建了它,它就已经映射到层次模型中了。如果你想阅读更多这是很好的文档
还可以减少指针间的跳转
const clearButtonInstance = buttonComponent.instance;
最重要的是bind
的使用。对于订阅者函数,您应该提供一个匿名函数或lambda函数。
// Listen to button clicks:
clearButtonInstance.clearButtonClick.subscribe(() => {
this.control.ngControl.control.setValue(EMPTY_VALUE);
(this.control as MatInput).focus();
});
正如你所看到的,我已经使用了chellappan建议的类型转换。老实说,我不认为这是正确的方法,但作为一个开始,它会对你有用。您最终可以将其更改为更合适的功能,或者只是使用ChildContent
和MatInput
,但无论哪种方式,在更复杂的组件(例如多个元素)中,您将难以确定您想要关注的元素,并且检查合格性将更加复杂,因此请注意。
作为奖励,请确保您将使用changeDetection: ChangeDetectionStrategy.OnPush
只要你可以。您将避免Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError
在生产中出现问题。这是我的更新版本的代码片段