Angular ng-content ไม่ทำงานกับ mat-form-field

Sep 15 2020

เป้าหมายของฉัน:

ฉันกำลังพยายามสร้าง 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 ไม่ถูกต้อง

ฉันไม่พบเอกสารสำหรับแท็ก 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;
  }

}

ขออภัยเมื่อฉันพยายามใช้สิ่งนี้ในรูปแบบมันยังคงล้มเหลวโดยมีข้อผิดพลาด "Error: mat-form-field must contain a MatFormFieldControl"

รหัสที่ฉันพยายามใช้ส่วนประกอบนี้ในรูปแบบ:

<mat-clearable-input>
    <mat-label>Numero incarico</mat-label>
    <buffered-input matInput formControlName="numero"></buffered-input>
</mat-clearable-input>

Repro บน 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 ต้องมี 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 โดยไม่ต้องใช้ความพยายามมากมาย

หากคุณมีแนวคิดเพิ่มเติมอย่าลังเลที่จะแยก stackblitz และลอง!

แก้ไขหลังจากคำตอบ @evilstiefel

<input matInput>วิธีการทำงานเฉพาะสำหรับพื้นเมือง เมื่อฉันลองแทนที่ด้วยองค์ประกอบการป้อนข้อมูลที่กำหนดเองมันไม่ทำงานอีกต่อไป

การตั้งค่าการทำงาน:

<mat-form-field appClearable>
    <mat-label>ID incarico</mat-label>
    <input matInput formControlName="id">
</mat-form-field>

การตั้งค่าเดียวกัน แต่มีคอมโพเนนต์ "buffered-input" ที่กำหนดเองของฉัน (ไม่ทำงาน :()

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

คำตอบ

1 evilstiefel Sep 29 2020 at 09:16

อีกวิธีหนึ่งคือการใช้คำสั่งเพื่อนำพฤติกรรมไปใช้

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 แต่คุณอาจต้องปรับให้เข้ากับกรณีการใช้งานของคุณ

2 Akash Sep 24 2020 at 12:46

อัปเดต :

ตัวเลือกที่ 1 ใช้ไม่ได้กับเวอร์ชันเชิงมุมใหม่เนื่องจาก@ViewChild()ส่งคืนไม่ได้กำหนดไว้ในngOnInit()hook การแฮ็กอีกอย่างคือการใช้ดัมมี่MatFormFieldControl-

ทางเลือกที่ 2

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

แก้ไข :

ข้อผิดพลาดนั้นเกิดขึ้นเนื่องจากMatFormFieldคอมโพเนนต์สอบถามเนื้อหาลูกที่ใช้@ContentChild(MatFormFieldControl)ซึ่งใช้ไม่ได้หากคุณใช้แบบซ้อนกันng-content( MatFormField ยังใช้การฉายภาพเนื้อหา )

ตัวเลือก 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;
    }
}

กรุณาชำระเงินstackBlitz นี้ นอกจากนี้ยังมีปัญหานี้ที่สร้างขึ้นgithubแล้ว