60

While creating a Model Driven Template Reactive forms, when I create model object from Form Value. Then model object is loosing its TYPE.

For a Simple Example:

Model Class Book:

export class Book {
  public name: string;
  public isbn: string;
}

Component:

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.css']
})
export class BookComponent implements OnInit {

  bookFormGroup: FormGroup;
  private newBook: Book = new Book();

  constructor(private fb: FormBuilder) {
    this.bookFormGroup = this.fb.group({
      name: new FormControl(''),
      isbn: new FormControl('')
    });
  }

  ngOnInit() {
  }

  addBook() {
    console.log('submit');
    this.newBook = <Book> this.bookFormGroup.value;
    console.log(this.newBook instanceof Book);
    console.log(this.newBook);
  }

}

HTML:

<form [formGroup]="bookFormGroup" (ngSubmit)="addBook()">
    <input type="text" formControlName="name" >
    <input type="text" formControlName="isbn" >

    <input type="submit" value="Submit">
</form>

In the above example, after filling newBook instance its converted to normal Object

i.e, After this.newBook = <Book> this.bookFormGroup.value;

this.newBook instanceof Book is becoming FALSE

How do I prevent this? Or is there any better way to achieve this?

Note: I tried with JSON.parse() but it still same.

Kishor Prakash
  • 8,011
  • 12
  • 61
  • 92
  • https://stackoverflow.com/questions/22885995/how-do-i-initialize-a-typescript-object-with-a-json-object https://stackoverflow.com/questions/13204759/typescript-or-javascript-type-casting – yurzui Apr 24 '18 at 09:19

11 Answers11

97

This constructor will work with any type and will assign any matching filed.

export class Book {
  public constructor(init?: Partial<Book>) {
        Object.assign(this, init);
    }
}

So you will be able to do this:

this.newBook = new Book(this.bookFormGroup.value);

This will save you a lot of work if the Book class will have any change in future and became bigger.

Kishor Prakash
  • 8,011
  • 12
  • 61
  • 92
Toshkata Tonev
  • 1,431
  • 11
  • 12
18

I use spread operator:

this.newBook = {...this.newBook,...this.bookFormGroup.value}
Husam Zidan
  • 619
  • 6
  • 18
  • Work only with attributs not with the function of the class – Hawklm CG Apr 07 '21 at 14:23
  • 3
    The form value which you will post to the server should not contain functions. @HawklmCG – Husam Zidan Apr 08 '21 at 08:00
  • I have several child components that all make up the model interface that I want to post to backend. Using this method, I could merge several forms that partially and sort of consecutively make up the model interface that I want to post. ```this.newBook = {...this.newBook, ...this.childForm1.bookFormGroup.value}; this.newBook = {...this.newBook, ...this.childForm2.someForm.value};``` where I have @ViewChild for my childForms. – Arizon Aug 18 '23 at 06:29
8

Let's say your model is like this:

export class Content {
    ID: number;
    Title: string;
    Summary: string;
}

Your component will look like this:

export class ContentComponent implements OnInit {


  content: Content;
  contentForm: FormGroup;

  ngOnInit() {


    this.contentForm = this.formBuilder.group({
      Title: ['', Validators.required],
      Summary: ['', Validators.required]
    });.....

When the save button is called, you can merge the form builder object and the dto you have:

onContentFormSubmit() {

// stop here if form is invalid
if (this.contentForm.invalid) {
  return;
}

this.content = Object.assign(this.content, this.contentForm.value);

}

this.content will have the predefined values you have from onInit and the values from the from group.

Raghav
  • 8,772
  • 6
  • 82
  • 106
6
  1. You should have an interface and a class, and the class should implement the interface.

  2. Create an empty book with an empty constructor.

    export class Book implements IBook {
      constructor(public name = '', public isbn = '') {}
    }
    
  3. Create a real model-driven form.

    this.bookFormGroup = this.fb.group(new Book());
    
  4. Correctly type your form

    this.newBook: IBook = this.bookFormGroup.value;
    
  • 2
    How do you add validation to bookFormGroup when we passed it a new Book() ? – Thibs Jun 28 '18 at 14:55
  • I never faced the case, but there's an [official documentation](https://angular.io/guide/dynamic-form#question-form-components) that might help you. If I had to do it, I would create a class method that accepts a form group as a parameter, and thus can set the validators. –  Jun 28 '18 at 14:59
5
let formData = this.formGroup.value as DataModel;
katesky8
  • 572
  • 6
  • 9
4

you can try this in your addBook():

 let formData = Object.assign({});
 formData = Object.assign(formData, this.bookFormGroup.value);
 this.newBook = new Book(formData.name ,formData.isbn );
 console.log(this.newBook instanceof Book);
Fateme Fazli
  • 11,582
  • 2
  • 31
  • 48
3

Change your class definition to

export class Book {
  constructor(public name: string, public isbn: string) {}
}

Change your addBook method to

addBook() {
  const { name, isbn } = this.bookFormGroup.value;
  this.newBook = new Book(name, isbn);
  console.log(this.newBook instanceof Book);
  console.log(this.newBook);
}
Tomasz Kula
  • 16,199
  • 2
  • 68
  • 79
2

I created a helper class with a static method like this:

export class ObjectAssigner {
  static create<T>(init: Partial<T>): T {
    const value = {};
    Object.assign(value, init);
    return value as T;
  }

With this way I can use it for both classes and interfaces like this:

const bookModel= ObjectAssigner.create<Book>(this.bookFormGroup.value);
Yirenkyi
  • 91
  • 1
  • 3
1

I have used 'Partical class' to inject value through constractor of class. this can be used when your form calls api and model needed to be passed in without casting or when you create new instance of model from reactive form value.

export class Address  {
  postCode: string;
  line1: string;
  line2: string;
  county: string;
  city: string;
  country: string;
  cityId: string;
  countryId: string;

  constructor(initialValues: Partial<Address> = {}) {
    if (initialValues) {
      for (const key in initialValues) {
        if (initialValues.hasOwnProperty(key)) {
           this[key] = initialValues[key];
        }
      }
    }
  }
}
Harry Sarshogh
  • 2,137
  • 3
  • 25
  • 48
0

I had this problem and create the formGroup directly to model, solved the problem. In your code:

  this.bookFormGroup = this.fb.group(Book);

Book is the class name of your model.

user1012506
  • 2,048
  • 1
  • 26
  • 45
0

I use:

this.formGroupSubscription = this.formGroup.valueChanges.subscribe(form => {
    let mappedObject: MyCreateDTO = form;

    // do stuff with mappedObject.myproperty
});
Astrophage
  • 1,231
  • 1
  • 12
  • 38