วิธีดูการเปลี่ยนแปลงแบบฟอร์มในเชิงมุม


151

ในเชิงมุมฉันอาจมีรูปแบบที่มีลักษณะดังนี้:

<ng-form>
    <label>First Name</label>
    <input type="text" ng-model="model.first_name">

    <label>Last Name</label>
    <input type="text" ng-model="model.last_name">
</ng-form>

ภายในตัวควบคุมที่สอดคล้องกันฉันสามารถดูการเปลี่ยนแปลงเนื้อหาของฟอร์มนั้นได้อย่างง่ายดาย:

function($scope) {

    $scope.model = {};

    $scope.$watch('model', () => {
        // Model has updated
    }, true);

}

นี่คือตัวอย่างเชิงมุมบน JSFiddle

ฉันมีปัญหาในการหาวิธีการทำสิ่งเดียวกันใน Angular เห็นได้ชัดว่าเราไม่มี$scope$ rootScope อีกต่อไป แน่นอนมีวิธีซึ่งสิ่งเดียวกันสามารถทำได้?

นี่คือตัวอย่างเชิงมุมบน Plunker


แทนที่จะดูข้อมูลบางส่วนจากคอนโทรลเลอร์ของคุณฉันคิดว่าคุณควรเริ่มต้นเหตุการณ์ (เช่นการเปลี่ยน NG เก่า) จากฟอร์มของคุณ
Deblaton Jean-Philippe

btw เหตุผลในการลบขอบเขตคือการกำจัดผู้เฝ้าดูเหล่านั้น ฉันไม่คิดว่าจะมีนาฬิกาซ่อนอยู่ที่ไหนสักแห่งในมุมที่ 2
Deblaton Jean-Philippe

4
ถ้าฉันเข้าใจคุณอย่างถูกต้องคุณกำลังแนะนำให้ฉันเพิ่มแอ(ngModelChange)="onModelChange($event)"ททริบิวต์ให้กับอินพุตทุกรูปแบบ
แก้วน้ำ

1
อินสแตนซ์ของฟอร์มไม่ปล่อยเหตุการณ์การเปลี่ยนแปลงบางอย่างหรือไม่ ถ้าเป็นเช่นนั้นคุณเข้าถึงได้อย่างไร
tambler

ถ้าฉันแน่ใจเกี่ยวกับสิ่งที่ฉันควรทำฉันจะตอบแทนที่จะแสดงความคิดเห็น ฉันยังไม่ได้ใช้ angular 2.0 แต่จากสิ่งที่ฉันได้อ่านนาฬิกาหายไปอย่างสมบูรณ์เพื่อให้มีกรอบงานตามเหตุการณ์ (แทนที่จะเป็นนาฬิกาลึกที่แสดงในแต่ละส่วนย่อย)
Deblaton Jean-Philippe

คำตอบ:


189

UPD คำตอบและการสาธิตได้รับการปรับปรุงให้สอดคล้องกับ Angular ล่าสุด


คุณสามารถสมัครรับการเปลี่ยนแปลงแบบฟอร์มทั้งหมดได้เนื่องจากFormGroup ที่เป็นตัวแทนของแบบฟอร์มแสดงvalueChangesคุณสมบัติซึ่งเป็นอินสแตนซ์ที่สังเกตการณ์ได้:

this.form.valueChanges.subscribe(data => console.log('Form changes', data));

ในกรณีนี้คุณจะต้องสร้างแบบฟอร์มด้วยตนเองโดยใช้FormBuilder บางสิ่งเช่นนี้

export class App {
  constructor(private formBuilder: FormBuilder) {
    this.form = formBuilder.group({
      firstName: 'Thomas',
      lastName: 'Mann'
    })

    this.form.valueChanges.subscribe(data => {
      console.log('Form changes', data)
      this.output = data
    })
  }
}

ลองใช้งานจริงvalueChangesในตัวอย่างนี้: http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview


2
ใกล้กับเครื่องหมายนี้มาก เพื่อยืนยัน - คุณกำลังบอกฉันว่ามันเป็นไปไม่ได้ที่จะสมัครสมาชิกvalueChangesอีซีแอลเหตุการณ์ของรูปแบบถ้าแบบฟอร์มนั้นมีการกำหนดภายในแม่แบบ? ในคำอื่น ๆ - ภายในคอนสตรัคเตอร์ขององค์ประกอบมันเป็นไปไม่ได้ที่จะได้รับการอ้างอิงถึงรูปแบบที่กำหนดไว้เพียงอย่างเดียวภายในแม่แบบขององค์ประกอบนั้นและไม่ได้ด้วยความช่วยเหลือของ FormBuilder?
แก้วน้ำ

นี่คือคำตอบที่ถูกต้อง หากคุณไม่มีเครื่องมือสร้างแบบฟอร์มคุณกำลังทำแบบฟอร์มที่ขับเคลื่อนด้วยเทมเพลต อาจเป็นวิธีที่ยังคงได้รับแบบฟอร์มการฉีด แต่ถ้าคุณต้องการแบบฟอร์มสังเกตค่าเปลี่ยนคุณควรใช้ formBuilder และทิ้งแบบจำลองแน่นอน
Angular University

2
@tambler คุณจะได้รับการอ้างอิงถึง NgForm @ViewChild()ใช้ ดูคำตอบที่อัปเดตของฉัน
Mark Rajcok

1
คุณไม่จำเป็นต้องยกเลิกการสมัครทำลาย
Bazinga

1
@galvan ฉันไม่คิดว่าจะมีการรั่วไหล แบบฟอร์มเป็นส่วนหนึ่งขององค์ประกอบและมันจะถูกกำจัดอย่างถูกต้องในการทำลายกับทุกฟิลด์และผู้ฟังเหตุการณ์
dfsq

107

หากคุณใช้งานอยู่FormBuilderให้ดูคำตอบของ @ dfsq

หากคุณไม่ได้ใช้FormBuilderมีสองวิธีในการแจ้งเตือนการเปลี่ยนแปลง

วิธีที่ 1

ตามที่กล่าวไว้ในความคิดเห็นเกี่ยวกับคำถามให้ใช้การผูกเหตุการณ์ในแต่ละองค์ประกอบอินพุต เพิ่มในแม่แบบของคุณ:

<input type="text" class="form-control" required [ngModel]="model.first_name"
         (ngModelChange)="doSomething($event)">

จากนั้นในองค์ประกอบของคุณ:

doSomething(newValue) {
  model.first_name = newValue;
  console.log(newValue)
}

แบบฟอร์มหน้าเว็บมีข้อมูลเพิ่มเติมบางอย่างเกี่ยวกับ ngModel ที่เกี่ยวข้องที่นี่:

ngModelChangeไม่ได้เป็น<input>เหตุการณ์องค์ประกอบ เป็นจริงคุณสมบัติเหตุการณ์ของNgModelคำสั่ง เมื่อแองกูลาร์เห็นเป้าหมายที่มีผลผูกพันในรูปแบบ[(x)]มันคาดว่าxคำสั่งที่จะมีxคุณสมบัติการป้อนข้อมูลและคุณสมบัติการxChangeส่งออก

อีกสิ่งที่แปลกประหลาดคือเทมเพลตนิพจน์, model.name = $event. เราเคยเห็น$eventวัตถุที่มาจากเหตุการณ์ DOM คุณสมบัติ ngModelChange ไม่สร้างเหตุการณ์ DOM มันเป็นเชิงมุมEventEmitterคุณสมบัติที่ส่งกลับค่ากล่องใส่เมื่อมันยิง ..

เรามักจะชอบ [(ngModel)]เรามักจะชอบเราอาจแบ่งการเชื่อมโยงถ้าเราต้องทำสิ่งพิเศษในการจัดการเหตุการณ์เช่น debounce หรือ throttle การกดแป้น

ในกรณีของคุณฉันคิดว่าคุณต้องการทำอะไรเป็นพิเศษ

วิธีที่ 2

ngFormกำหนดตัวแปรแม่แบบท้องถิ่นและตั้งค่าให้
ใช้ ngControl กับองค์ประกอบอินพุต
รับการอ้างอิงถึงคำสั่ง NgForm ของแบบฟอร์มโดยใช้ @ViewChild จากนั้นสมัครสมาชิกกลุ่มควบคุมของ NgForm สำหรับการเปลี่ยนแปลง:

<form #myForm="ngForm" (ngSubmit)="onSubmit()">
  ....
  <input type="text" ngControl="firstName" class="form-control" 
   required [(ngModel)]="model.first_name">
  ...
  <input type="text" ngControl="lastName" class="form-control" 
   required [(ngModel)]="model.last_name">

class MyForm {
  @ViewChild('myForm') form;
  ...
  ngAfterViewInit() {
    console.log(this.form)
    this.form.control.valueChanges
      .subscribe(values => this.doSomething(values));
  }
  doSomething(values) {
    console.log(values);
  }
}

plunker

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่ 2 ให้ดูวิดีโอ Savkin ของ

ดูคำตอบของ @ Thierry สำหรับข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่คุณสามารถทำได้กับสิ่งที่valueChangesสังเกตได้ (เช่นการ debouncing / รอสักครู่ก่อนที่จะประมวลผลการเปลี่ยนแปลง)


61

เพื่อให้ได้คำตอบที่ยอดเยี่ยมมาก่อนหน้านี้อีกเล็กน้อยคุณต้องระวังว่าใช้ประโยชน์จากสิ่งที่สังเกตได้เพื่อตรวจจับและจัดการการเปลี่ยนแปลงค่า มันเป็นสิ่งที่สำคัญและทรงพลังจริงๆ ทั้ง Mark และ dfsq อธิบายแง่มุมนี้ในคำตอบของพวกเขา

ตัวอย่างที่อนุญาตให้ใช้ไม่เพียง แต่จะใช้subscribeวิธีการ (สิ่งที่คล้ายกับthenวิธีการของสัญญาใน Angular 1) คุณสามารถไปต่อได้หากจำเป็นต้องใช้โซ่การประมวลผลบางอย่างสำหรับข้อมูลที่อัพเดทในรูปแบบ

ฉันหมายความว่าคุณสามารถระบุเวลา debounce ระดับนี้ด้วยdebounceTimeวิธีการ สิ่งนี้ช่วยให้คุณสามารถรอเวลาก่อนที่จะจัดการกับการเปลี่ยนแปลงและจัดการอินพุตหลายอย่างได้อย่างถูกต้อง:

this.form.valueChanges
    .debounceTime(500)
    .subscribe(data => console.log('form changes', data));

นอกจากนี้คุณยังสามารถเสียบการประมวลผลที่คุณต้องการทริกเกอร์ได้โดยตรง (เช่นแบบอะซิงโครนัสบางตัว) เมื่อมีการอัปเดตค่า ตัวอย่างเช่นหากคุณต้องการจัดการค่าข้อความเพื่อกรองรายการตามคำขอ AJAX คุณสามารถใช้switchMapวิธีการดังต่อไปนี้:

this.textValue.valueChanges
    .debounceTime(500)
    .switchMap(data => this.httpService.getListValues(data))
    .subscribe(data => console.log('new list values', data));

คุณสามารถดำเนินการต่อไปได้ด้วยการเชื่อมโยงสิ่งที่ส่งคืนที่สังเกตได้โดยตรงไปยังคุณสมบัติของส่วนประกอบของคุณ:

this.list = this.textValue.valueChanges
    .debounceTime(500)
    .switchMap(data => this.httpService.getListValues(data))
    .subscribe(data => console.log('new list values', data));

และแสดงโดยใช้asyncท่อ:

<ul>
  <li *ngFor="#elt of (list | async)">{{elt.name}}</li>
</ul>

เพียงเพื่อบอกว่าคุณต้องคิดวิธีจัดการกับรูปแบบที่แตกต่างกันใน Angular2 (วิธีที่มีประสิทธิภาพยิ่งขึ้น ;-))

หวังว่าจะช่วยให้คุณ Thierry


ไม่มี 'valueChanges' ในประเภท 'string'
Toolkit

ฉันติดขัดในไฟล์ TS แล้วควรวางวิธีตรวจสอบการเปลี่ยนแปลงแบบฟอร์มที่ใด หากเราเก็บไว้ใน ngAfterViewInit () {} ดูเหมือนว่า this.form.valueChanges จะโทรติดต่อเสมอหากเราต้องการใช้โซ่การประมวลผลบางส่วนสำหรับข้อมูลที่ปรับปรุงแล้วในรูปแบบ
TàiNguyễn

1

ขยายคำแนะนำของ Mark ...

วิธีที่ 3

ใช้การตรวจจับการเปลี่ยนแปลง "ลึก" บนโมเดล ข้อดีส่วนใหญ่เกี่ยวข้องกับการหลีกเลี่ยงการรวมด้านส่วนติดต่อผู้ใช้ลงในส่วนประกอบ สิ่งนี้ยังจับการเปลี่ยนแปลงทางโปรแกรมที่เกิดขึ้นกับโมเดล ที่กล่าวว่าจะต้องใช้งานพิเศษเพื่อดำเนินการสิ่งต่าง ๆ เช่นการ debouncing ตามที่แนะนำโดย Thierry และสิ่งนี้จะจับการเปลี่ยนแปลงทางโปรแกรมของคุณเองดังนั้นใช้ด้วยความระมัดระวัง

export class App implements DoCheck {
  person = { first: "Sally", last: "Jones" };
  oldPerson = { ...this.person }; // ES6 shallow clone. Use lodash or something for deep cloning

  ngDoCheck() {
    // Simple shallow property comparison - use fancy recursive deep comparison for more complex needs
    for (let prop in this.person) {
      if (this.oldPerson[prop] !==  this.person[prop]) {
        console.log(`person.${prop} changed: ${this.person[prop]}`);
        this.oldPerson[prop] = this.person[prop];
      }
    }
  }

ลองใน Plunker


1

สำหรับ5+รุ่นเชิงมุม การนำเวอร์ชันมาใช้ช่วยทำให้เกิดการเปลี่ยนแปลงเชิงมุมได้มาก

ngOnInit() {

 this.myForm = formBuilder.group({
      firstName: 'Thomas',
      lastName: 'Mann'
    })
this.formControlValueChanged() // Note if you are doing an edit/fetching data from an observer this must be called only after your form is properly initialized otherwise you will get error.
}

formControlValueChanged(): void {       
        this.myForm.valueChanges.subscribe(value => {
            console.log('value changed', value)
        })
}

0

ฉันคิดเกี่ยวกับการใช้วิธีการ (ngModelChange) จากนั้นคิดเกี่ยวกับวิธีการ FormBuilder และตัดสินในที่สุดในรูปแบบของวิธีที่ 3 ซึ่งจะบันทึกการตกแต่งเทมเพลตด้วยคุณสมบัติพิเศษและรับการเปลี่ยนแปลงรูปแบบโดยอัตโนมัติ - ลดความเป็นไปได้ที่จะลืมบางสิ่ง ด้วยวิธีที่ 1 หรือ 2

ลดความซับซ้อนของวิธีที่ 3 บิต ...

oldPerson = JSON.parse(JSON.stringify(this.person));

ngDoCheck(): void {
    if (JSON.stringify(this.person) !== JSON.stringify(this.oldPerson)) {
        this.doSomething();
        this.oldPerson = JSON.parse(JSON.stringify(this.person));
    }
}

คุณสามารถเพิ่มการหมดเวลาเพื่อเรียกใช้ doSomething () หลังจาก x จำนวนมิลลิวินาทีเพื่อจำลองการดีบัค

oldPerson = JSON.parse(JSON.stringify(this.person));

ngDoCheck(): void {
    if (JSON.stringify(this.person) !== JSON.stringify(this.oldPerson)) {
        if (timeOut) clearTimeout(timeOut);
        let timeOut = setTimeout(this.doSomething(), 2000);
        this.oldPerson = JSON.parse(JSON.stringify(this.person));
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.