在自定义指令中使用ViewChild/ContentChild在mat-form-field中查找input[matIn



在我们的应用程序中有许多mat-form-field元素。其中部分包含MatSelect下拉菜单,但大部分包含具有matInput属性的input[type=text]元素。

我有一个指令ClearButtonDirective与非常一般的selector: 'mat-form-field'-这是有意的,所以它修改了所有的mat-form-fields。指令插入了一个"清除"按钮。到所有mat-form-field元素中,当单击时清除输入元素的值,它只在文本字段中有一些值时出现。

我有两个问题,这两者都是有关的事实,我不知道如何找到输入元素与@ViewChild@ContentChild装饰器(说实话,我还不确定这两者之间的区别是什么)。

  1. onClearClick()中,值被清除(使用MatFormFieldControl子),但是我还想关注输入字段之后——怎么做?我需要它的参考资料…
  2. 我只想要指令对那些包含input[matInput]mat-form-fields采取行动(插入清除按钮),但不是其他,例如那些包含mat-select-如何?再次,为此我需要能够检测/找到input[matInput]子。

我不想修改现有的模板来实现这一点,所以我不能添加任何元素引用或CSS类。

StackBlitz的工作示例—前三个盒子是mat-form-fields和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建议的类型转换。老实说,我不认为这是正确的方法,但作为一个开始,它会对你有用。您最终可以将其更改为更合适的功能,或者只是使用ChildContentMatInput,但无论哪种方式,在更复杂的组件(例如多个元素)中,您将难以确定您想要关注的元素,并且检查合格性将更加复杂,因此请注意。

作为奖励,请确保您将使用changeDetection: ChangeDetectionStrategy.OnPush只要你可以。您将避免Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError

在生产中出现问题。这是我的更新版本的代码片段

相关内容

  • 没有找到相关文章

最新更新