yabab-dev / ng2-ckeditor

Angular2 CKEditor component
MIT License
358 stars 96 forks source link

editorChange is firing on load, causing the formControl to be dirty #236

Closed sir-captainmorgan21 closed 6 years ago

sir-captainmorgan21 commented 6 years ago

Here is the HTML

<div *ngIf="ckeditorLoaded" class="form-group">
<label class="label-margin">Job Description</label>
<ckeditor name="description" formControlName="description" [config]="ckEditorConfig" debounce="500" (editorChange)="test($event)"></ckeditor>
<small class="text-red" *ngIf="!postingForm.get('description').value && !postingForm.get('description').pristine">Please add a job description.</small>
</div>

Here is the config object

export const CKEDITOR_CONFIG = {
  toolbar: [
    { name: 'basicstyles', items : ['Bold', 'Italic'] },
    { name: 'paragraph', items : ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote'] },
  ],
  format_tags: 'p;h2;h3;h4',
  allowedContent: true,
  extraAllowedContent: 'style[type,media]'
};

Here is the component. Sorry for the size, but I wanted to give you full context. At the bottom you'll see the test(event) function. This is being called twice on load of the component once ckeditor is initialized. There is no code to modify the description control on the form. test(event) is tied to editorChange event.

/*
* findIndex needs a Polyfill on IE, make sure we have one!
*/

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subscription, forkJoin } from 'rxjs';
import { map } from 'rxjs/operators';
import { ToasterService } from 'angular2-toaster';

// shared services for the app
import { ABCService } from '@go-shared/services/abc.service';
import { AuthenticationService } from '@go-services/authentication';
import { OmnitureService, OMNITURE_EVENT } from '@go-shared/omniture';
import { LayoutService, ScriptService } from '@go-shared/services';

// api routes required for this page
import { PostingsService, IPosting } from '@go-services/postings';
import { Position } from '@go-shared/models';

import {
  CustomersLocationService,
  CustomersPositionsService,
  CustomerAccountService
} from '@go-services/customers';

import { QualificationsService } from '@go-services/qualifications';

import { BillingAccountsService } from '@go-app/services';
// reference data
import {
  REFERENCE_DATA_INDUSTRIES,
  REFERENCE_DATA_STATES,
  REFERENCE_DATA_TIMEZONES,
  REFERENCE_DATA_JOB_TYPES
} from 'snagajob-lib/dist/snagajob';

import {
  CUSTOMER_POSTING_STATUS
} from '@go-data/customer-posting-status';

import {
  POSTING_STATUS
} from '@go-data/index';

import {
  TypeaheadData,
  TypeaheadOption
} from '@go-shared/modules/typeahead/typeahead.component';

import {
  REQ_FORM_VALIDS,
  LOCATION_FORM,
  PST_FORM_VALIDS,
  POSTINGS_FORM
} from './posting.form';

import {
  PASSIVE_FILTERS_VALUES,
  PASSIVE_FILTERS,
  REQUIREMENT_LEVEL,
  PASSIVE_LEVELS,
  ID_TYPES,
  SUBMIT_TYPES,
 } from './data';

import {
  QUALIFICATIONS_CATEGORIES,
  AGE_OPTIONS,
  EXPERIENCE_OPTIONS,
  UNSELECTED,
  INDUSTRY_OPTIONS,
  GROUP_INDUSTRIES
} from '@go-data/configurable-qualifications';

import {
  IProduct,
  IBillingAccount,
  IQualification,
  ICurrentQualification
} from '@go-services/index';

import { ModalTypes } from '@go-app/modules/recruiting/postings-new/data';
import { PRODUCT_STATUSES } from '@go-app/services/billing-accounts/data';
import { REGEX } from 'app/data/regex';
import { CKEDITOR_CONFIG } from '@go-app/config/ckeditor.config';
import { environment } from '@go-environment/environment';
@Component({
  selector: 'recruiting-postings-detail',
  styleUrls: ['./postings.detail.component.scss'],
  templateUrl: './postings.detail.component.html'
})

export class RecruitingPostingsDetailComponent implements OnInit, OnDestroy {

  FORM_VALIDS = null;
  industries = REFERENCE_DATA_INDUSTRIES;
  states = REFERENCE_DATA_STATES;
  timezones = REFERENCE_DATA_TIMEZONES;
  STATUS = POSTING_STATUS;
  PRODUCT_STATUS = PRODUCT_STATUSES;
  CUSTOMER_STATUS = CUSTOMER_POSTING_STATUS;
  minimumExperienceOptions = EXPERIENCE_OPTIONS;
  minimumAgeOptions = AGE_OPTIONS;
  qualificationsCategories = QUALIFICATIONS_CATEGORIES;
  possibleAnswers = INDUSTRY_OPTIONS;
  possibleQualifications = GROUP_INDUSTRIES;
  public UNSELECTED = UNSELECTED;

  ckEditorConfig = CKEDITOR_CONFIG;

  postingForm: FormGroup;
  locationForm: FormGroup;

  typeaheadPositions: TypeaheadData[];
  typeaheadLocations: TypeaheadData[];

  sub: Subscription;
  postingId: string = '';
  posting: any = {};
  locations: any[] = [];
  positions: any[] = [];
  jskId: number;
  companyId: String = '';
  postingStatusId: string = '';
  billingAccountStatus: number;

  mobile: boolean = false;
  requisitionLoaded = false;
  locationsLoaded = false;
  isPending: boolean = false;
  purchaseCTA: boolean = false;
  postLocationName: string = '';
  postPositionName: string = '';
  showGetStartedModal: boolean = false;
  pendingPosition: boolean = false;
  private prestinePassiveFilters;

  billingAccount: IBillingAccount;
  workingPostingId: string;
  // RegExp
  rNoUrl: RegExp = REGEX.noUrl;
  rZipCode: RegExp = REGEX.zipCode;

  allQualifications: IQualification[] = [];
  currentQualifications: ICurrentQualification[] = [];

  objectKeys = Object.keys;

  private event = OMNITURE_EVENT;
  private ckeditorLoaded = false;
  showLocationModal = false;
  showPositionModal = false;
  showQualificationsModal = false;

  public showModal: boolean = false;
  public modalTypes = ModalTypes;
  public modalType: ModalTypes;
  public submitting: boolean = false;

  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private layout: LayoutService,

    // @go-services
    private auth: AuthenticationService,
    private postingService: PostingsService,
    private customerAccountService: CustomerAccountService,
    private billingAccountService: BillingAccountsService,
    private qualificationsService: QualificationsService,
    private lcs: CustomersLocationService,
    private pcs: CustomersPositionsService,

    // Third-party services
    private toasterService: ToasterService,
    private omniture: OmnitureService,
    private scripts: ScriptService,
    public abcService: ABCService

  ) {
    this.billingAccountService.billingAccount
      .subscribe((account: any) => {
        this.billingAccount = account;
      });

    this.sub = this.route.params.subscribe((params) => {

      this.postingId = params['id'];
      this.postingForm = POSTINGS_FORM;
      this.FORM_VALIDS = PST_FORM_VALIDS;
      this.locationForm = LOCATION_FORM;

      this.showGetStartedModal = params['getstarted'];
      this.mobile = this.layout.device === 'mobile';
      this.layout.deviceSizeChanged$.subscribe((d: string) => this.mobile = (d === 'mobile'));

      if (this.isNewPosting()) {
        this.getLocationsPostions().subscribe();
        this.postingForm.reset();
        this.postingForm.get('isPartTime').setValue(true);
        this.requisitionLoaded = true;
        if (this.abcService.has('configurable-qualifications')) {
          this.setQualificationsDefaults();
        }
        this.generateQualificationsForm();
      } else {
        const posting$ = this.getPosting(this.postingId);
        posting$.subscribe(
          (r: any) => {
            this.postingLoaded();
          },
          (error) => {
            this.toasterService.pop('error', 'Unable to retrieve your posting.');
          }
        );
      }
    });

  }

  ngOnInit(): void {
    if (this.showGetStartedModal) {
      // evar/prop 39 - Number of postings
      this.omniture.setNumOfPostings('1');
    }
    this.scripts.load('ckeditor').then((loaded) => {
      this.ckeditorLoaded = true;
    });

    if (this.isNewPosting()) {
      this.omniture.fireEvents(OMNITURE_EVENT.ENUMCREATEPOSTINGSTART);
      this.postingForm.get('passiveFilters').get('minimumAge').setValue(UNSELECTED);
      this.postingForm.get('passiveFilters').get('minimumExperience').setValue(UNSELECTED);
    }

    this.postingForm.get('description').statusChanges.subscribe(
      (result: any) => {
        alert('description status change');
      }
    );
  }

  postingLoaded() {
    this.requisitionLoaded = true;

    this.getLocationsPostions().subscribe(() => {
      // set location/position typeaheads (sets form controls for us and populates view with data)

      if (this.positions.length === 0) {
        this.postingForm.get('positionId').setValue('');
      }

      this.selectPosition(this.postingForm.get('positionId').value);
      this.selectLocation(this.postingForm.get('locationId').value);
    });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }

  getPosting(id): Observable<any> {
    return this.postingService.getByIdEdge(id)
      .pipe(
        map((response: any) => {
          if (!response) {
            this.toasterService.pop('error', 'Oops, we had trouble loading your posting.');
          } else {
            const postingFormValue = {};

            Object.keys({ ...response }).forEach((key) => {
              if (key === 'passiveFilters') {
                const controlArray = <FormGroup> this.postingForm.get('passiveFilters');
                for (const p of response.passiveFilters) {
                  const type = PASSIVE_FILTERS_VALUES[p.type];
                  controlArray.setControl(type, new FormControl(p.value));
                }
              }
              if (this.postingForm.get(key)) {
                postingFormValue[key] = response[key];
              }
            });

            // set values after form is ready
            this.postingForm.patchValue(postingFormValue);
            this.companyId = response.customerId;
            this.postingStatusId = response.statusId;
            this.jskId = response.jskPostingId;

            /* For every Configurable Qualification being returned we add to our
            * associative array using the qualification name as the key.
            */
            if (response.configurableQualifications !== null) {
              this.currentQualifications = response.configurableQualifications;
            }
          }
        })
      );
  }

  onSubmit({ value, valid }: { value: Position, valid: boolean }) {
    this.submitting = true;
    this.prestinePassiveFilters = value.passiveFilters;
    value.passiveFilters = this.formatPassiveFilters(value);
    this.validatePositionsAndSubmit(value);
  }

  // format passive values
  formatPassiveFilters(value: Position) {
    const filters: any = [];
    Object.keys({ ...value.passiveFilters }).forEach((key) => {
      const option = value.passiveFilters[key];
      const type = PASSIVE_FILTERS[key];
      const level = PASSIVE_LEVELS[type];

      const f = {
        type,
        level: option ? level : REQUIREMENT_LEVEL.OPTIONAL,
        value: +option || 0 // '+' operator converts into a number
      };
      filters.push(f);
    });
    return filters;
  }

  validatePositionsAndSubmit(formValues) {
    if (!this.validForm()) {
      if (this.postingForm.get('positions').get('name').value === '') {
        this.toasterService.pop('error', `Please create a position.`);
      }
      this.submitting = false;
      return;
    }

    // check if positions exist
    if (!formValues.positionId) {
      const newPosition = formValues.positions;
      newPosition.industryId = formValues.industry;
      newPosition.locationIds = [formValues.locationId];
      newPosition.wage = 0;
      newPosition.color = '1';

      // create position
      this.pcs.post(newPosition)
        .subscribe(
          (r: any) => {
            // submit
            this.pendingPosition = true;
            this.postingForm.get('positionId').setValue(r.id);
            formValues.positionId = r.id;
            this.processPostingRequest(formValues);
          },
          (error) => {
            this.submitting = false;
            this.toasterService.pop('error', `Sorry, we could not create the position.`);
          }
        );
    } else {
      this.processPostingRequest(formValues);
    }
  }

  processPostingRequest(form: FormGroup) {
    const formValues: any = form;
    const valid: boolean =  form.valid;

    if (!this.validForm()) {
      return;
    }

    // add posting id to form
    formValues.id = this.postingId;

    const submitType = this.updatePostingType(); // check if we should create or update posting

    // check if they have a location Id that has been made
    if (this.postingForm.get('locationId').value === 'new') {
      window.scroll(0, 0);
      this.postingForm.get('locationId').setValue('');
      return false;
    }

    // check if the position is tied to the location if not make it so
    if (!this.pendingPosition) {
      let posValid = null;
      const position = this.positions.find(pos => pos.id === this.postingForm.get('positionId').value);
      if (position) {
        posValid = position.locationIds.find(id => id === this.postingForm.get('locationId').value);
      }

      if (!posValid) {
        position.locationIds.push(this.postingForm.get('locationId').value);
        this.pcs.put(position).subscribe();
      }
    }

    formValues.configurableQualifications = this.currentQualifications;
    delete formValues.positions;

    if (Object.keys(SUBMIT_TYPES).find((prop: string) => SUBMIT_TYPES[prop] === submitType)) {

      if (submitType === SUBMIT_TYPES.create) {
        // event20 - GO Create Posting Save
        this.omniture.fireEvents(OMNITURE_EVENT.ENUMCREATEPOSTINGSAVE);
      }

      const apiType: string = !this.isNewPosting() ? 'put' : 'postEdge';
      const serviceType: string = 'postingService';
      this[serviceType][apiType](formValues)
        .subscribe(
          (posting: IPosting) => {
            this.postingId = posting.id;
            formValues.passiveFilters = this.prestinePassiveFilters;
            if (this.purchaseCTA === true) {
              this.purchasePosting(posting);
            } else {
              this.routeToPostingPage(submitType, posting);
            }
          },
          (error) => {
            if (error.messages) {
              for (const message of error.messages) {
                this.toasterService.pop('warning', message.text);
              }
            } else {
              this.toasterService.pop('warning', error.message);
            }
            this.submitting = false;
          }
        );
    } else {
      this.submitting = false;
      this.resetForm(form);
    }
  }

  routeToPostingPage(type, posting) {
    window.scroll(0, 0);
    this.toasterService.pop('success', 'Posting saved.');
    let navigateParams =  { };
    if (type === SUBMIT_TYPES.update) {
      if (posting.statusId === POSTING_STATUS.ACTIVE || posting.postingStatusId === POSTING_STATUS.ACTIVE) {
        navigateParams =  {
          livePostingUpdateModal: true
        };
      }
    }
    this.submitting = false;
    this.router.navigate(['recruiting/postings-new'], { queryParams: navigateParams });
  }

  purchasePosting(posting) {
    window.scroll(0, 0);
    const type = this.purchasePostingType(); // get purchase type (first purchase or buy)
    if (type === SUBMIT_TYPES.purchase) {
      this.workingPostingId = posting.id;
      this.openModal(this.modalTypes.Buy);
    }

    if (type === SUBMIT_TYPES.firstPurchase) {
      this.submitting = false;
      this.router.navigate(['/recruiting/postings-new/purchase'], { queryParams: { postingId: posting.id,  page: 'detail' } });
    }
  }

  addLocationModal() {
    this.showLocationModal = true;
  }

  hideLocationModal(form) {
    this.showLocationModal = false;
    if (this.postingForm.get('locationId').value === 'new') {
      this.postingForm.get('locationId').setValue('');
    }
    form.reset();
  }

  hideGetStartedModal() {
    this.omniture.setEvents(null);
    this.omniture.clearEvents();
    this.showGetStartedModal = false;
  }

  /**
   * Function to pop the modal for new position
   * @param value
   */
  addPositionModal() {
    this.showPositionModal = true;
  }

  /**
   * Function to add the position to the requisition as well as gather the new list of positions after adding.
   * @param position
   */
  addPosition(position) {
    this.postingForm.get('positionId').setValue(position.id);
    this.pcs.get().subscribe((p: any) => {
      this.positions = p;
      this.setTypeaheads();
    });
  }

  /**
   * Function to get both locations and positions.
   */
  getLocationsPostions() {
    return forkJoin(
      this.pcs.get(),
      this.lcs.get()
    )
      .pipe(
        map((data: any) => {
          this.positions = data[0];
          this.locations = data[1];
          this.locationsLoaded = true;
            // Set Typeahead Positions/Locations
          this.setTypeaheads();
        })
      );
  }

  validForm() {
    if (this.postingForm.invalid) {
      let errorMessage: string = '';
      // check which fields are invalid, and show toaster notification
      Object.keys(this.postingForm.controls).forEach((key) => {
        const controlErrors = this.postingForm.get(key).errors;
        if (controlErrors != null) {
          Object.keys(controlErrors).forEach((keyError) => {
            if (this.FORM_VALIDS[key] && this.FORM_VALIDS[key][keyError]) {
              errorMessage = this.FORM_VALIDS[key][keyError].error;
            }
          });
        }
      });
      if (errorMessage == '') { errorMessage = 'Please ensure all qualifications have options.'; }
      this.toasterService.pop('error', errorMessage);
      return false;
    }

    return true;
  }

  addLocation({ value, valid }: { value: Location, valid: boolean }) {
    this.lcs.post(value)
      .subscribe(
        (r: any) => {
          this.toasterService.pop('success', 'Location added');
          // refresh locations
          this.lcs.get().subscribe((l: any) => {
            this.locations = l;
            this.setTypeaheads();
            this.postingForm.controls['locationId'].setValue(r.id);
            // hide modal
            this.showLocationModal = false;
          });
        },
        (error: any) => {
          this.toasterService.pop('warning', error.message);
        }
      );
  }

  getProduct(postingId): IProduct {
    return !this.isNewPosting()  ? this.billingAccountService.getProductByPostingId(postingId) : null;
  }

  setTypeaheads() {
    this.setPositionTypeahead();
    this.setLocationTypeahead();

    // Select position/location if there is only one
    if (this.positions.length === 1) { this.selectPosition(this.positions[0].id); }
    if (this.locations.length === 1) { this.selectLocation(this.locations[0].id); }
  }

  setPositionTypeahead() {
    const positions: TypeaheadOption[] = this.positions.map((position) => {
      return {
        name: position.name,
        value: position.id
      };
    });

    return this.typeaheadPositions = [{
      name: 'Positions',
      options: positions
    }];
  }

  setLocationTypeahead() {
    const locations: TypeaheadOption[] = this.locations.map((location) => {
      return {
        name: location.name,
        value: location.id
      };
    });

    return this.typeaheadLocations = [{
      name: 'Locations',
      options: locations
    }];
  }

  selectLocation(e) {
    const location = this.locations.find(l => l.id === e);
    // Set location name to view, and id to form
    if (location) {
      this.postingForm.get('locationId').setValue(location.id);
      this.postLocationName = location.name;
    } else {
      this.postLocationName = 'Pick location';
    }
  }

  selectPosition(e) {
    const position = this.positions.find(l => l.id === e);
    // Set position name to view, and id to form
    if (position) {
      this.postingForm.get('positions').get('name').setValue(' ');
      this.postingForm.get('positionId').setValue(position.id);
      this.postPositionName = position.name;
    } else {
      this.postPositionName = 'Pick position';
    }
  }

  hidePositionModal(e) {
    this.showPositionModal = false;
    if (this.postingForm.get('positionId').value === 'new') {
      this.postingForm.get('positionId').setValue('');
    }
  }

  resetForm(form) {
    this.getPosting(this.postingId).subscribe();
    Object.keys(form.controls).forEach((control: any) => {
      form.controls[control].markAsPristine();
    });
  }

  /**
   * getSubmitPostingType checks the status of the billing account,
   * and returns which action should be performed
   * first-purchase (direct-post page) or purchase (buy modal)
   */
  purchasePostingType() {
    return this.billingAccount.creditCardRefId ? SUBMIT_TYPES.purchase : SUBMIT_TYPES.firstPurchase;
  }

  /**
   * updatePostingType - if posting is 'new' create/POST posting,
   * otherwise update/PUT posting
   */
  updatePostingType() {
    return this.postingId === ID_TYPES.new ? SUBMIT_TYPES.create : SUBMIT_TYPES.update;
  }

  cancel(form) {
    this.toasterService.pop('info', 'Posting was not updated.');
    this.resetForm(form);
  }

  /**
   * purchase button/action was clicked
   */
  purchaseBtnCTA() {
    this.purchaseCTA = true;
  }

  goToPosting(): void {
    window.open(`${environment.jobseeker}/job-seeker/jobs/job-details.aspx?postingid=${this.jskId ? this.jskId : this.postingId}`, '_blank');
  }

  postingReady() {
    if (this.isNewPosting()) {
      return true;
    }

    return this.postingReady;
  }

  isNewPosting() {
    return this.postingId === ID_TYPES.new || this.postingId === ID_TYPES.newActive;
  }

  ltEventCancel() {
    // event19 - GO Create Posting Cancel
    this.omniture.setEvents(OMNITURE_EVENT.ENUMCREATEPOSTINGCANCEL);
  }

  openModal(modalType: ModalTypes) {
    this.modalType = modalType;
    this.showModal = true;
  }

  closeModal() {
    this.showModal = false;
    this.submitting = false;
  }

  copyToPosition(value: string) {
    const position = this.positions.find(findPosition => findPosition.name === value);
    if (position) {
      this.postingForm.get('positions').get('name').setValue(' ');
      this.postingForm.get('positionId').setValue(position.id);
      this.postPositionName = position.name;
    } else {
      this.postingForm.get('positions').get('name').setValue(value);
      this.postingForm.get('positionId').setValue(null);
      this.postPositionName = '';
    }
  }

  showBuyButton() {
    const product: IProduct = this.getProduct(this.postingId);
    let productStatus: PRODUCT_STATUSES;
    if (product) {
      productStatus = this.getProduct(this.postingId).status;
    }
    return !product || (productStatus !== PRODUCT_STATUSES.Live) && (productStatus !== PRODUCT_STATUSES.Pending);
  }

  addQualificationsModal() { this.showQualificationsModal = true; }
  hideQualificationsModal() { this.showQualificationsModal = false; }
  updateOptionDropdown(event) { this.postingForm.get('configurableQualifications').get(event.target.name).setValue(event.target.value); }

  generateQualificationsForm() {
    const qualificationForm = <FormGroup> this.postingForm.get('configurableQualifications');
    qualificationForm.reset();

    // Create form controls, setting values if they exist in currentQualifications
    this.currentQualifications.forEach((qual: ICurrentQualification) => {
      qualificationForm.setControl('name_' + qual.name + (qual.option ? qual.option : ''), new FormControl());
      qualificationForm.setControl('requirement_' + qual.name + (qual.option ? qual.option : ''), new FormControl('' + qual.requirement));
    });

    this.qualificationsService.get().subscribe((results: any) => {
      this.allQualifications = results.list;
      this.allQualifications.unshift({
        name: 'q-exp-yrs-industry',
        category: 3,
        selectionType: 4,
        displayText: '_ of experience in the _ industry',
        possibleAnswers: this.possibleAnswers,
        possibleQualifications: this.possibleQualifications
      });
    });
  }

  updateQualificationPreference(qualName: string, qualOption: number, newValue: string) {
    const index = this.currentQualifications.findIndex(qualification => qualification.name === qualName && qualification.option == qualOption);
    const formId = qualName + (qualOption ? qualOption : '');

    this.currentQualifications[index].requirement = newValue == 'true' ? true : false;
    this.postingForm.get('configurableQualifications').get('requirement_' + formId).setValue(newValue);
  }

  removeQualification(qualName: string, qualOption: number) {
    this.currentQualifications = this.currentQualifications.filter(
      qualification => !(qualName === qualification.name && qualOption == qualification.option)
    );
    const formId = qualName + (qualOption ? qualOption : '');

    this.postingForm.get('configurableQualifications').get('name_' + formId).setValue(false);
    this.postingForm.get('configurableQualifications').get('requirement_' + formId).clearValidators();
    this.postingForm.get('configurableQualifications').get('requirement_' + formId).updateValueAndValidity();
  }

  displayQualificationText(qualName: string, qualOption: number = null) {
    const qualData = this.allQualifications.find(qualification => qualification.name == qualName);
    if (qualData) {
      if (qualOption && !this.isEmptyObject(qualData.possibleAnswers)) {
        return qualData.displayText.split('_')[0] +  // first part of the string
          '<em>' + qualData.possibleAnswers[qualOption] + '</em>' + // text of the option
          qualData.displayText.split('_')[1]; // second part of the string
      } else {
        return qualData.displayText;
      }
    }
  }

  trashQualificationCallback(event) {
    this.removeQualification(event.name, event.option);
  }

  qualificationCallback(event) {
    const qualificationForm = <FormGroup> this.postingForm.get('configurableQualifications');
    this.currentQualifications = event;

    // rebuild the form based on new current Qualifications state
    this.currentQualifications.forEach((qual: ICurrentQualification) => {
      qualificationForm.setControl('name_' + qual.name + (qual.option ? qual.option : ''), new FormControl());
      qualificationForm.setControl('requirement_' + qual.name + (qual.option ? qual.option : ''), new FormControl('' + qual.requirement));
    });
  }

  setQualificationsDefaults() {
    this.currentQualifications.push({ name: 'q-basic-dob', option: 2, requirement: false });
    this.currentQualifications.push({ name: 'q-basic-auth', requirement: true });
  }

  isEmptyObject(obj) {
    return (obj && (Object.keys(obj).length === 0));
  }

  minimumAgeClick() {
    this.postingForm.get('passiveFilters').get('minimumAge').setValue(0);
  }

  minimumExperienceClick() {
    this.postingForm.get('passiveFilters').get('minimumExperience').setValue(0);
  }

  test(event) {
    console.log(event);
  }

}
kzimny commented 6 years ago

Can you use the (change)="onChange($event)" event instead of (editorChange)="test($event)" ? Check the demo application. change event is firing after the content change.

kzimny commented 6 years ago

No more activity. I propose to close the issue.