<commit_before>import { AbstractControl, FormGroup } from "@angular/forms";
import { BioFormControl } from "./forms/BioFormControl";
import { Game } from "../models/Game";
import { Developer } from "../models/Developer";
import { Topic } from "../models/Topic";
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/forkJoin";
import { Repository } from "./Repository";
import { Parent } from "../models/base/Parent";
import { Child } from "../models/base/Child";
import { HttpErrorResponse } from "@angular/common/http";
import { ObjectMapper } from "json-object-mapper";
import { RestResult } from "./RestResult";
import deserialize = ObjectMapper.deserialize;
import { HostListener } from "@angular/core";

export abstract class FormComponent<TModel, TResultModel> {
  public success = false;
  public hasErrors = false;
  public hasChanges = false;

  public isNew = false;

  public formLoaded = false;

  public formGroup: FormGroup;

  public model: TModel;

  initForm() {
    this.formGroup = new FormGroup(this.getFormGroupConfig());

    // noinspection TsLint
    for (const controlIndex in this.formGroup.controls) {
      this.updateControlValue(controlIndex, this.model[controlIndex]);
    }

    this.subscribeToFormChanges();

    this.formLoaded = true;
  }

  @HostListener('window:beforeunload', ['$event'])
  doSomething($event) {
    if (this.hasChanges) {
      $event.returnValue = 'Форма не была сохранена. Данные будут потеряны!';
    }
  }

  subscribeToFormChanges() {
    const myFormValueChanges$ = this.formGroup.valueChanges;

    myFormValueChanges$.subscribe(x => {
      const changes = {};
      for (const k in x) {
        if (!x.hasOwnProperty(k)) {
          continue;
        }
        if (this.model[k] !== x[k]) {
          this.hasChanges = true;
          this.hasErrors = false;
          this.success = false;
          const orig = this.model[k];
          this.model[k] = x[k];
          changes[k] = {
            old: orig,
            current: x[k]
          };
          (<BioFormControl>this.formGroup.controls[k]).ServerErrors = [];
        }
      }
      if (changes) {
        this.processChanges(changes);
      }
    });
  }

  public save() {
    this.success = false;
    this.hasChanges = false;
    let result;
    if (this.isNew) {
      result = this.doAdd();
    } else {
      result = this.doUpdate();
    }
    result.subscribe(
      (saveResult: TResultModel) => {
        this.success = true;
        this.processSuccessSave(saveResult);
      },
      e => {
        this.hasErrors = true;
        this.handleSubmitError(e);
      }
    );
  }

  protected abstract getFormGroupConfig(): { [key: string]: AbstractControl };

  protected abstract processSuccessSave(saveResult: TResultModel);

  protected abstract doAdd(): Observable<TResultModel>;<commit_msg>#33 don't clear hasChanges on error<commit_after>import { AbstractControl, FormGroup } from "@angular/forms";
import { BioFormControl } from "./forms/BioFormControl";
import { Game } from "../models/Game";
import { Developer } from "../models/Developer";
import { Topic } from "../models/Topic";
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/forkJoin";
import { Repository } from "./Repository";
import { Parent } from "../models/base/Parent";
import { Child } from "../models/base/Child";
import { HttpErrorResponse } from "@angular/common/http";
import { ObjectMapper } from "json-object-mapper";
import { RestResult } from "./RestResult";
import deserialize = ObjectMapper.deserialize;
import { HostListener } from "@angular/core";

export abstract class FormComponent<TModel, TResultModel> {
  public success = false;
  public hasErrors = false;
  public hasChanges = false;

  public isNew = false;

  public formLoaded = false;

  public formGroup: FormGroup;

  public model: TModel;

  initForm() {
    this.formGroup = new FormGroup(this.getFormGroupConfig());

    // noinspection TsLint
    for (const controlIndex in this.formGroup.controls) {
      this.updateControlValue(controlIndex, this.model[controlIndex]);
    }

    this.subscribeToFormChanges();

    this.formLoaded = true;
  }

  @HostListener('window:beforeunload', ['$event'])
  doSomething($event) {
    if (this.hasChanges) {
      $event.returnValue = 'Форма не была сохранена. Данные будут потеряны!';
    }
  }

  subscribeToFormChanges() {
    const myFormValueChanges$ = this.formGroup.valueChanges;

    myFormValueChanges$.subscribe(x => {
      const changes = {};
      for (const k in x) {
        if (!x.hasOwnProperty(k)) {
          continue;
        }
        if (this.model[k] !== x[k]) {
          this.hasChanges = true;
          this.hasErrors = false;
          this.success = false;
          const orig = this.model[k];
          this.model[k] = x[k];
          changes[k] = {
            old: orig,
            current: x[k]
          };
          (<BioFormControl>this.formGroup.controls[k]).ServerErrors = [];
        }
      }
      if (changes) {
        this.processChanges(changes);
      }
    });
  }

  public save() {
    this.success = false;
    let result;
    if (this.isNew) {
      result = this.doAdd();
    } else {
      result = this.doUpdate();
    }
    result.subscribe(
      (saveResult: TResultModel) => {
        this.hasChanges = false;
        this.success = true;
        this.processSuccessSave(saveResult);
      },
      e => {
        this.hasErrors = true;
        this.handleSubmitError(e);
      }
    );
  }

  protected abstract getFormGroupConfig(): { [key: string]: AbstractControl };

  protected abstract processSuccessSave(saveResult: TResultModel);

  protected abstract doAdd(): Observable<TResultModel>;
{'fraction_non_alphanumeric': 0.13219393093826298, 'fraction_numerical': 0.0003487966515521451, 'mean_word_length': 2.782981530343008, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7525868', 'n_tokens_mistral': 1713, 'n_tokens_neox': 1672, 'n_words': 384}