Angular ng-content 不适用于 mat-form-field
Posted
技术标签:
【中文标题】Angular ng-content 不适用于 mat-form-field【英文标题】:Angular ng-content not working with mat-form-field 【发布时间】:2021-01-02 00:22:06 【问题描述】:我的目标:
我正在尝试使用清除按钮构建可重复使用的 mat-form-field。
我是如何努力实现目标的:
我创建了一个“mat-clearable-input”组件并像这样使用它:
<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>
预期结果:
ng-content 标签获取标签和输入并将它们放入 mat-form-field 标签内。
实际结果:
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)
我好像遗漏了一些东西,而且我没有正确使用 ng-content 标签。
我无法在 Angular 网站上找到 ng-content 标签的文档。
感谢您的帮助。
在下面回答后编辑
所以我尝试了这个建议的方法:
export class MatClearableInputComponent implements OnInit
@ContentChild(MatFormFieldControl) _control: MatFormFieldControl<any>;
@ViewChild(MatFormField) _matFormField: MatFormField;
// see https://***.com/questions/63898533/angular-ng-content-not-working-with-mat-form-field/
ngOnInit()
this._matFormField._control = this._control;
不幸的是,当我尝试在表单中使用它时,它仍然失败并显示错误“错误:mat-form-field 必须包含 MatFormFieldControl。”
我尝试在表单中使用此组件的代码:
<mat-clearable-input>
<mat-label>Numero incarico</mat-label>
<buffered-input matInput formControlName="numero"></buffered-input>
</mat-clearable-input>
stackblitz 上的重现:https://stackblitz.com/edit/angular-material-starter-xypjc5?file=app/clearable-form-field/clearable-form-field.component.html
注意 mat-form-field 功能如何不起作用(没有轮廓,没有浮动标签),同时打开控制台,您会看到错误 Error: mat-form-field must contain a MatFormFieldControl。
选项 2 发布后编辑
我试过这样做:
<mat-form-field>
<input matInput hidden>
<ng-content></ng-content>
</mat-form-field>
它可以工作,但是当我在表单字段中添加一个 mat-label 时,如下所示:
<mat-clearable-input>
<mat-label>Numero incarico</mat-label>
<buffered-input matInput formControlName="numero"></buffered-input>
</mat-clearable-input>
标签永远不会浮动,它只是作为一个正常的跨度一直停留在那里。
所以我尝试将带有标签的内容子项分配给 this._matFormField._control._label
,但这不起作用,因为 _label 是私有的并且没有设置器。
看起来我很不走运,如果不付出很多努力,这是无法在 Angular 中完成的。
如果您有任何进一步的想法,请随时 fork stackblitz 并尝试!
@evilstiefel 回答后编辑
该解决方案仅适用于原生 <input matInput>
。
当我尝试用我的自定义输入组件替换它时,它不再起作用了。
工作设置:
<mat-form-field appClearable>
<mat-label>ID incarico</mat-label>
<input matInput formControlName="id">
</mat-form-field>
相同的设置,但使用我的自定义“缓冲输入”组件(不工作:()
<mat-form-field appClearable>
<mat-label>ID incarico</mat-label>
<buffered-input matInput formControlName="id"></buffered-input>
</mat-form-field>
当我单击清除按钮时,控制台会记录此错误:
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)
【问题讨论】:
如果你想在mat-form-field
中使用组件(在这种情况下,将mat-clearable-input
放入其中)它必须实现MatFormFieldControl
接口。看看the docs 看看怎么做。
@julianobrasil 谢谢,但这不是问题所在:在问题的代码 sn-p 中,我使用了带有 matInput 指令的输入标签,因此它应该在 mat-form-field 中工作.我不是在创建自定义组件。还要记住,如果我不使用 ng-content 并且我只是手动将输入标签放在那里,这段代码就可以工作
也许stackblitz会有所帮助
一个问题:它应该是什么样子?
@AndreEirico 喜欢本页的第一个示例material.angular.io/components/input/examples
【参考方案1】:
另一种解决方案是使用指令来实现行为。
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();
这将创建一个名为 appClearable
的指令和一个用于备用布局的可选组件。确保将组件和指令添加到模块的 declarations
-array 中。您可以指定用于提供用户界面的模板,也可以仅使用ClearButtonComponent
作为一种万能的解决方案。标记如下所示:
<!-- 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>
无论有无 ngControl/FormControl,这都适用,但您可能需要根据您的用例对其进行调整。
【讨论】:
这是有效的,但只有当我像这样使用它时。@ContentChild()
的类型从MatInput
更改为MatFormFieldControl
,就会出现这种情况。我的版本最初只适用于像 input
或 textarea
这样的原生元素。我已经相应地更新了我的答案。
谢谢,成功了。您是否也碰巧知道如何将清除按钮 HTML 放在 mat 表单字段的内部而不是作为兄弟?我需要按钮成为 mat 表单字段的子项,以便我可以相对于 mat 表单字段定位它。
也许看看***.com/questions/38093727 的策略。不幸的是,ContentChild 的viewContainerRef
不能像我的例子那样被调用。您可以通过访问本机元素来使其工作,但如果可能的话,我会避免这样做。【参考方案2】:
更新:
选项 1 不适用于新的 Angular 版本,因为 @ViewChild()
在 ngOnInit()
挂钩中返回 undefined。另一个技巧是使用虚拟MatFormFieldControl
-
选项 2
<mat-form-field>
<input matInput hidden>
<ng-content></ng-content>
</mat-form-field>
编辑:
抛出该错误是因为MatFormField
组件使用@ContentChild(MatFormFieldControl)
查询子内容,如果您使用嵌套的ng-content
(MatFormField also uses content projection),这将不起作用。
选项 1(已弃用)
下面是如何使它工作 -
@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;
请查看this stackBlitz。另外,this issue 已经在github
中创建。
【讨论】:
谢谢,看来这行得通,再问一个问题:为什么/如何行得通? 那是因为angular material
在AfterContentInit
期间无法通过ng-content
自行查询MatFormFieldControl
(我不知道为什么)。查看来自angular material
的form-field.ts
文件,其中抛出了错误并查看调用层次结构。在上述解决方案中,我们显式地执行MatFormField
组件为获取MatFormFieldControl
- github.com/angular/components/blob/… 而执行的相同操作。
需要注意的一点是MatFormField
组件也使用了内容投影,并且可能因为多个内容投影而无法查询MatFormFieldControl
。我只是猜测 - github.com/angular/components/blob/…
我想我是对的。 @ContentChild()
不适用于嵌套内容投影。由于角度材质也使用投影,如果您还使用内容投影,则无法找到 MatFormFieldControl - ***.com/questions/38060342/…
完美答案,也许您可以将其添加到答案中。看起来它适用于许多其他情况。谢谢!以上是关于Angular ng-content 不适用于 mat-form-field的主要内容,如果未能解决你的问题,请参考以下文章
Angular - 将输入传递给 ng-content 组件
使用 ng-content 动态创建 angular2 组件
使用 ng-content 动态创建 angular2 组件
Angular 10 ng-content 投影导致表格冻结