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>

ma​​t-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 回答后编辑

该解决方案仅适用于原生 &lt;input matInput&gt;。 当我尝试用我的自定义输入组件替换它时,它不再起作用了。

工作设置:

<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,这都适用,但您可能需要根据您的用例对其进行调整。

【讨论】:

这是有效的,但只有当我像这样使用它时。 ID incarico 如果我尝试在自定义表单控件上使用它,它不起作用。我将对显示问题的问题进行另一次编辑 啊。如果您将@ContentChild() 的类型从MatInput 更改为MatFormFieldControl,就会出现这种情况。我的版本最初只适用于像 inputtextarea 这样的原生元素。我已经相应地更新了我的答案。 谢谢,成功了。您是否也碰巧知道如何将清除按钮 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 materialAfterContentInit 期间无法通过ng-content 自行查询MatFormFieldControl(我不知道为什么)。查看来自angular materialform-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 投影导致表格冻结

Angular 6 MatSnackBar 适用于 Chrome 但不适用于 IE11

angular4:ng-content 基于嵌套类的选择