【问题标题】:How to check for changes in form in Angular 2 using如何使用 Angular 2 检查表单的变化
【发布时间】:2017-04-20 08:23:57
【问题描述】:

我有一个包含少量数据字段和两个按钮的表单。我只想在用户对表单进行一些更改时启用按钮。我试过使用:

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

但最初在表单加载时也会检测到更改。有没有其他方法可以检查表格中的任何更改。我希望它仅在用户更改字段时调用,而不是在加载表单时调用。以下是我的 html 和 typescript 代码:

profile.html:

<section>
    <div>
        <form [formGroup]="form">
            <fieldset>
                <div class="panel-group m-l-1 m-r-1 accordion vertical-scroll" id="">
                    <div class="form-group required no-gutter">
                        <label for="firstname"> First Name:</label>
                        <div class="col-md-7 col-lg-6">
                            <input type="text" class="form-control" id="firstname" placeholder="" name="firstname" title="firstname" formControlName="firstname" size="128" aria-required="true" maxlength="35">
                        </div>
                    </div>
                </div>

            </fieldset>
            <div>
                <button class="btn btn-primary" type="button" (click)="save()">Save</button>
                <button class="btn btn-primary" type="button" (click)="cancel()">Cancel</button>
            </div>
        </form>
    </div>
</section>

profile.component.ts:

export class ProfileComponent implements OnInit, AfterViewInit, OnChanges {
    public form: FormGroup;

    constructor(private formBuilder: FormBuilder, private app: Application) {

    }

    loadForm(): void {
        this.form = this.formBuilder.group({
            firstname: [this.app.firstName, Validators.required]
        });
        this.form.valueChanges.subscribe(data => console.log('form changes', data));

    }

    save(): void {

    }

    cancel(): void {

    };

    ngOnInit() {
        this.loadForm();
    }

    ngAfterViewInit() {
        this.loadForm();
    }
}

【问题讨论】:

标签: angular typescript


【解决方案1】:

您可以使用.dirty(或.pristine)值来确定用户是否使用 UI 更改了控件值:

<button class="btn btn-primary" type="button" (click)="save()" [disabled]="!form.dirty" >Save</button>
<button class="btn btn-primary" type="button" [disabled]="!form.dirty"(click)="cancel()">Cancel</button>

https://angular.io/docs/ts/latest/api/forms/index/AbstractControl-class.html#!#dirty-anchor

dirty : boolean 如果用户更改了值,则控件是脏的 在用户界面中。

请注意,对控件值的编程更改不会标记它 脏。

touched : boolean 一个控件被标记为一旦用户触摸 触发了一个模糊事件。

【讨论】:

    【解决方案2】:

    .dirty 和 .pristine 布尔值的问题在于,一旦它们发生变化,即使您撤消引入的所有更改,它们也不会返回。我设法找到了解决此问题的方法,方法是创建一个监视整个表单更改的类,并将更改的值与原始表单值进行检查。这样,如果用户更改被撤消,表单可以返回原始状态,或者可以选择在您可以提供和订阅的可观察对象 (ReplaySubject) 上发出布尔值。

    使用会是这样的:

    private _formIntactChecker:FormIntactChecker;
    
    constructor(private _fb: FormBuilder) { 
    
        this._form = _fb.group({
            ...
         });
    
        // from now on, you can trust the .dirty and .pristine to reset
        // if the user undoes his changes.
        this._formIntactChecker = new FormIntactChecker(this._form);
    
    }
    

    或者,除了重置 .pristine/.dirty 布尔值,该类可以配置为在表单从完整更改为已修改时发出布尔值,反之亦然。真正的布尔值意味着表单恢复原样,而假布尔值意味着表单不再完整。

    这是一个关于如何使用它的示例:

    private _formIntactChecker:FormIntactChecker;
    
    constructor(private _fb: FormBuilder) { 
    
         this._form = _fb.group({
            ...
         });
    
         var rs = new ReplaySubject()
    
         rs.subscribe((isIntact: boolean) => {
            if (isIntact) {
                // do something if form went back to intact
            } else {
                // do something if form went dirty
            }
         })
    
         // When using the class with a ReplaySubject, the .pristine/.dirty
         // will not change their behaviour, even if the user undoes his changes,
         // but we can do whatever we want in the subject's subscription.
         this._formChecker = new FormIntactChecker(this._form, rs);
    
    }
    

    最后,完成所有工作的类:

    import { FormGroup } from '@angular/forms';
    import { ReplaySubject } from 'rxjs';
    
    export class FormIntactChecker {
    
        private _originalValue:any;
        private _lastNotify:boolean;
    
        constructor(private _form: FormGroup, private _replaySubject?:ReplaySubject<boolean>) {
    
            // When the form loads, changes are made for each control separately
            // and it is hard to determine when it has actually finished initializing,
            // To solve it, we keep updating the original value, until the form goes
            // dirty. When it does, we no longer update the original value.
    
            this._form.statusChanges.subscribe(change => {
                if(!this._form.dirty) {
                    this._originalValue = JSON.stringify(this._form.value);
                }
            })
    
            // Every time the form changes, we compare it with the original value.
            // If it is different, we emit a value to the Subject (if one was provided)
            // If it is the same, we emit a value to the Subject (if one was provided), or
            // we mark the form as pristine again.
    
            this._form.valueChanges.subscribe(changedValue => {
    
                if(this._form.dirty) {
                    var current_value = JSON.stringify(this._form.value);
    
                    if (this._originalValue != current_value) {
                        if(this._replaySubject && (this._lastNotify == null || this._lastNotify == true)) {
                            this._replaySubject.next(false);
                            this._lastNotify = false;
                        }
                    } else {
                        if(this._replaySubject)
                            this._replaySubject.next(true);
                        else
                            this._form.markAsPristine();
    
                        this._lastNotify = true;
                    }
                }
            })
        }
    
        // This method can be call to make the current values of the
        // form, the new "orginal" values. This method is useful when
        // you save the contents of the form but keep it on screen. From
        // now on, the new values are to be considered the original values
        markIntact() {
            this._originalValue = JSON.stringify(this._form.value);
    
            if(this._replaySubject)
                this._replaySubject.next(true);
            else
                this._form.markAsPristine();
    
            this._lastNotify = true;
        }
    }
    

    重要提示:注意初始值

    该类使用JSON.stringify() 快​​速比较整个formGroup 值对象。但是,在初始化控件值时要小心。

    例如,对于复选框,您必须将绑定它的值设置为布尔值。如果使用其他类型,如“checked”、“0”、“1”等,将无法正常进行比较。

    <input type="checkbox" ... [(ngModel)]="variable"> <!-- variable must be a boolean -->
    

    &lt;select&gt; 也是如此,您必须将其值绑定到字符串,而不是数字:

    <select ... [(ngModel)]="variable"> <!-- variable must be a string -->
    

    对于常规文本输入控件,也可以使用字符串:

    <input type="text" ... [(ngModel)]="variable"> <!-- variable must be a string -->
    

    这是一个示例,否则它将无法正常工作。假设您有一个文本字段,并使用整数对其进行初始化。原始值的字符串化将是这样的:

    { field1: 34, field2: "一些文本字段" }

    但是,如果用户将 field1 更新为不同的值并返回 34,则新的字符串化将是:

    { field: "34", field2: "some text field" }

    如您所见,虽然表单并没有真正改变,但由于数字 34 周围的引号,原始值和新值之间的字符串比较会导致 false。

    【讨论】:

    • 我最近读到,除了@Output() 之外,您不应该使用EventEmitter。我已更新代码并将EventEmitter 替换为ReplaySubject
    • 我希望它内置了这个功能:-/尤其是“字符串化”问题
    • 您可以为 JSON.stringify 设置一个“替换器”函数,该函数可用于强制数字变为字符串。这应该使比较更加可靠:JSON.stringify(model, function(i,val){if (typeof(val)!=='object') return val.toString(); else return val;})
    • 如果要忽略大小写,也可以在toString()之后添加.toUpperCase()
    • 感谢您提供此解决方案。它可以很好地跟踪表单更改并有助于保持我的组件代码干净。
    【解决方案3】:

    首先使用“NgForm”。
    &lt;form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)"&gt;
    然后在“onSubmit()”函数上执行此操作 -

    onSubmit(myForm: NgForm): void {
      let formControls = myForm.controls;
      if(formControls.firstName.dirty) {
        console.log("It's dirty");
      }
      else {
        console.log("not dirty");
      }
    } 
    

    它肯定会起作用。您可以打印整个“myForm”并亲自查看所有可用选项。

    【讨论】:

      【解决方案4】:

      我猜你可以忽略第一个变化

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

      提示:导入skip 运算符

      【讨论】:

      • 是的,我们可以使用跳过运算符,但是当我形成负载时,它会调用该事件 4 次。因此,对于每种形式,我应该知道事件被调用了多少次,并在 skip 中使用该数字。
      • 这个比较麻烦,但是我不知道更好的办法。
      • 检查脏污和触摸会更好,IMO。这样做的好处是可以忽略以编程方式对表单进行的更改。
      • 您是否尝试过使用statusChanges 更适合您?
      【解决方案5】:

      我对我的代码使用了一些技巧,我认为这不是最好的解决方案,但不知何故它对我有用。

      profile.component.ts:

      tempForm: any
      ngOnInit() {
        this.loadForm();
        this.tempForm = this.form.value
      }
      
      save(): void {
        if (this.tempForm === this.form.value) {
          // Do Save
        } else {
          // Value is Same as initial
        }
      }
      

      希望这能解决您的问题,或者只是提供一些灵感。

      【讨论】:

      • 三等号不是在这里检查相等性的可靠方法:adripofjavascript.com/blog/drips/…
      • 是的,谢谢,您纠正了这不是一个可靠的方法,我说这不是最好的解决方案。但它对我有用,我从你的链接 jsfiddle.net/z37tqefa 中引用这个只是为了证明上面的代码是有效的。
      【解决方案6】:

      尝试以下操作,看看表单是否发生了变化:

      ngOnChanges() {
          if (!!this.form && this.form.dirty) {
              console.log("The form is dirty!");
          }
          else {
              console.log("No changes yet!");
          }      
      }  
      

      【讨论】:

      • 记得参考你正在工作的formGroup
      【解决方案7】:

      我设法通过修改变量来解决这个问题:

      <button ion-button icon-only clear type="submit" [disabled]="!modified || !editForm.valid">
          <ion-icon name="checkmark"></ion-icon>
      </button>
      

      然后在输入上设置 ionChange 事件的修改变量:

      <ion-input type="text" (ionChange)="modified=true"></ion-input> 
      

      【讨论】:

        【解决方案8】:

        您可以将{ emitEvent: false } 作为以下反应式表单方法的选项传递,以防止它们触发 valueChanges 事件

        this.form.patchValue(value, { emitEvent: false })
        
        this.form.setValue(value, { emitEvent: false })
        
        this.form.controls.email.updateValueAndValidity({ emitEvent: false })
        
        this.form.disable({ emitEvent: false })
        

        是的,禁用触发 valueChanges 事件

        PS:以上this.form是一种反应形式

        阅读这篇精彩的文章,它会回答你所有的问题,甚至对反应式表单提供一些深刻的见解:

        https://netbasal.com/angular-reactive-forms-tips-and-tricks-bb0c85400b58

        【讨论】:

          【解决方案9】:

          您可以像这样检查特定表单控件中的更改:

          this.profileForm.controls['phone'].valueChanges.subscribe(
                          data => console.log('form changes', data)
          
                          );
          

          【讨论】:

            【解决方案10】:

            您可以在提交时将您的对象与表单的结果进行比较

            let changes = false;
            for ( let [ key, value ] of Object.entries( this.form.value ) ) {
                const form = this.form.value;
                const record = this.record;
                if ( form[ key ] != record[ key ] ) {
                    changes = true;
                    break;
                }
            }
            if ( !changes ) {
                // No changes
            } else {
                this.record = this.form.value;
                this.UpdateRecord();
            }
            

            【讨论】:

            • 这只会检查细微的差异。也许:if ( form[ key ] != record[ key ] ) { 将其更改为 if ( JSON.stringify(form[ key ]) !== JSON.stringify(record[ key ]) ) {,即使这样在某些情况下也不够
            猜你喜欢
            • 2017-02-15
            • 2018-11-17
            • 2017-05-04
            • 2017-02-09
            • 1970-01-01
            • 1970-01-01
            • 2017-04-02
            • 2019-08-04
            • 2018-04-19
            相关资源
            最近更新 更多