4

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.

  • I'm kind of confused with what youre exactly trying to do? and what you are currently experiencing? – JBoothUA Nov 09 '18 at 04:21
  • 1
    @JBoothUA I added more info – Gabriel Veloso Nov 09 '18 at 04:22
  • But basically as I said, I am trying to map my form fields to my monkey model. But, as you can see, the monkey model has 2 fields (pubId and driversLicense) both of them are documents. But the form has only 1 field (the user can specify any of these two document options), and I need to set to my model property (pubId or driversLicense) depending of what the 'doc' field represents on my model (I can know this based on the 'doc' field length, as commented on the model) – Gabriel Veloso Nov 09 '18 at 04:26
  • You can dynamically add/remove form controls later after initialization. Is that what you are looking for? – Amit Chigadani Nov 09 '18 at 04:28
  • 1
    @AmitChigadani nop, I am trying to set the correct field on my Monkey model. That depends on the length of the 'doc' field in the form. After the user submits I need to check either if 'doc' has `length===8` (set driversLicense) or 13 (set pubId) – Gabriel Veloso Nov 09 '18 at 04:30
  • It is possible with your model, but I have one question: do you have any validation on the property? if yes, please let me know, so that I can include validation in the example, which I will share with you and also update car model properties in you question. – Ajay Ojha Nov 09 '18 at 04:44
  • @AjayOjha not in this form, but there are other forms that sure can have a Validators.required or some other, so please, include that possibility – Gabriel Veloso Nov 09 '18 at 04:46
  • Ok thanks, I will add nested FormGroup of car object. – Ajay Ojha Nov 09 '18 at 04:47

2 Answers2

2

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.

JBoothUA
  • 2,465
  • 3
  • 15
  • 40
  • 1
    This solves but adds business logic to component, which for me is highly undesired. By the way, the 'doc' field is the one that represents either pubId or driversLicense – Gabriel Veloso Nov 09 '18 at 04:57
  • as far as I know if you want it "reactive" you'll have to add some logic to the formGroup property on your component – JBoothUA Nov 09 '18 at 05:10
-1

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 );
  }
}
  • I have created another example based on your code provide code with minor changes. I have removed FormModel and generate the FormGroup with RxFormBuilder. Here is the link https://stackblitz.com/edit/model-based-angular-reactive-form-value-changes-with-rxw-y6dwaf?file=src%2Fapp%2Fmonkey.model.ts – Ajay Ojha Nov 09 '18 at 08:16