Angular ReactiveForm to Model Mapping

Viewed 6946

I've been developing an app for a month now. I have come across many problems and almost all of them I've found solutions instead of opening threads but there is one design problem I still haven't figured out.

Suppose I have a small component called MonkeyComponent, it just has a form for my model (Monkey)

export class Car {
  model: string
}


export class Monkey {
  // If doc contains 8 digits
  driversLicense?: string;

  // If doc contains 13 digits
  pubId?: string;

  name: string;
  age: number;
  car: Car; // another model (could be added to form later)
}


export class AppComponent {
  formGroup: FormGroup;

  constructor(private fb: FormBuilder, private store: MonkeyStore) {
    this.formGroup = this.fb.group({
      name: [''],
      doc: [''],
      age: [''],
    });
  }

  save() {
    // now I need to map my form value to my monkey model
    // but they are mismatched (because doc can be used to populate
    // either pubId or driversLicense)
  }
}

This form mapping is common in many models of my project (one field of the form representing another field in the model)

Also I can't create multiple fields (client requirement)

How would you create this mapping? I am open to design suggestions (class model is not required if you have a better option)

Is there a Reactive-way to do this?

Is it avoidable to not have to use Object.assign and then manually mapping the divergent fields?

A clean solution would be to find a way to this.formGroup.value be

{
   pubId: '1234567890123',
   name: 'Miwalkey',
   age: 12,
}

or

{
   driversLicense: '12345678',
   name: 'Miwalkey',
   age: 12,
}

depending on the value (length) of doc.

2 Answers

I think this would be the designed approach:

ngOnInit() {
    this.formGroup.valueChanges.subscribe(val => {
         if (val.doc.length === 13) {
             monkey.pubId = val.doc;
         } else {
             monkey.driversLicense = val.doc;
         }
    });
}

or you could do

this.formGroup.get('doc').valueChanges.subscribe(val => {
       if (val.length === 13) {
         monkey.pubId = val;
       } else {
         monkey.driversLicense = val;
       }
  });

also if you're using ngModel you could put the same logic inside of an (ngModelChange) callback.

My current solution is this (I find this VERY ugly)

Base Form Model

export class FormModel {
  constructor(input?: any) {
    if (input) {
      this.fromJSON(input);
    }
  }

  fromJSON(input: any): this {
    return Object.assign(this, input);
  }
}

Car (model)

export class Car extends FormModel {
  model: string;
}

Monkey (model)

export class Monkey extends FormModel {
  pubId?: string;
  driversLicense?: string;

  car: Car;
  name: string;
  age: number;

  fromJSON(input: any): this {
    super.fromJSON(input);

    this.setDoc(input.doc);
    this.car = new Car(input.car);

    return this;
  }

  setDoc(doc: string) {
    if (doc.length === 8) {
      this.driversLicense = doc;
    } else if (doc.length === 13) {
      this.pubId = doc;
    }

    delete this['doc'];
  }
}

MonkeyComponent

export class MonkeyComponent {
  formGroup: FormGroup;

  constructor(private fb: FormBuilder, private store: MonkeyStore) {
    this.formGroup = this.fb.group({
      name: [''],
      doc: [''],
      age: [''],
    });
  }

  save() {
    const monkey = new Monkey(this.formGroup.value);
    console.log(monkey );
  }
}
Related