角度ng-content不适用于mat-form-field

时间:2020-09-15 09:03:20

标签: javascript angular angular-material

我的目标:

我正在尝试使用一个清除按钮来构建可重复使用的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://stackoverflow.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>

堆栈闪电的复制: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必须包含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中完成。

如果您有其他任何想法,请随意进行堆叠尝试并尝试!

在@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)

2 个答案:

答案 0 :(得分:2)

更新

选项1不适用于新的角度版本,因为@ViewChild()ngOnInit()挂钩中返回undefined。另一个技巧是使用虚拟MatFormFieldControl-

选项2

<mat-form-field>
  <input matInput hidden>
  <ng-content></ng-content>
</mat-form-field>

修改

抛出该错误是因为MatFormField组件使用@ContentChild(MatFormFieldControl)查询子内容,如果您使用嵌套的ng-contentMatFormField 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。另外,已经在github中创建了this issue

答案 1 :(得分: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的指令和一个用于后备布局的可选Component。确保将组件和指令添加到模块的declarations数组中。您可以指定用于提供用户界面的模板,也可以仅使用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的情况下都可以使用,但是您可能需要根据使用情况进行调整。

相关问题