1

I'm creating an edit form in angular and I'm confused about the lifecycle of the object that is returned from my backend server. When I make a call to my service in ngOnInit(), I get workable data. When I assign that to an instance variable, It is undefined in initForm() even though I'm calling initForm() after assigning that data.

If you look at the console.log statements you will see that the object works inside the ngOnInit() function but not in initForm(). What am I missing here? How do I make this.data accessible by the entire component?

payee-edit.component.ts:

export class PayeeEditComponent implements OnInit, OnDestroy {
  public payee: Payee;
  id: number;
  payeeEditForm: FormGroup;
  data: Payee;
  subscription: Subscription;

  constructor(private router: Router, private route: ActivatedRoute, private payeeService: PayeeService) { }
  ngOnInit() {
    this.route.params
      .subscribe(
        (params: Params) => {
          this.id = +params['id'];
          this.subscription = this.payeeService.getPayee(this.id).subscribe(
            data => {
              this.data = data;
              console.log(this.data.company); //THIS WORKS
            }
          );
        }
      );
    this.initForm();

  }
  private initForm() {
    console.log(this.data.company); //THIS RETURNS UNDEFINED
    let payeeCompany = 'this.data.company';
    let payeeFirstName =  'this.data.first_name;'
    let payeeLastName = 'this.data.last_name;'
    let payeeNotes = 'this.data.notes;'

    this.payeeEditForm = new FormGroup({
      'company': new FormControl(payeeCompany),
      'first_name': new FormControl(payeeFirstName),
      'last_name': new FormControl(payeeLastName),
      'notes': new FormControl(payeeNotes)
    });
  }

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

payee.service.ts:

  getPayee(id: number) {
    return this.http.get(`http://localhost:3000/payees/${id}`)
    .map(data => data.json());
  }

Edit:
If I put the call to initForm() inside the subscription, I get an error on page load complaining that initForm() needs to be called. It doesn't work until I fire the event that triggers the getPayee() call. I understand now why I'm getting the behavior now, so thank you for the link. I just need a little help with my specific use case

ctilley79
  • 2,151
  • 3
  • 31
  • 64
  • If I put the call to `initForm()` inside the subscription, I get an error on page load complaining that `initForm()` needs to be called. It doesn't work until I fire the event that triggers the `getPayee()` call. I understand now why I'm getting the behavior now, so thank you for the link. I just need a little help with my specific use case. – ctilley79 Apr 07 '17 at 13:05
  • What is the exact error message? And I just noticed you are using string values... `let payeeCompany='this.data.company'` will be the string literal. You should use: `let payeeCompany=this.data.company` But you could also just assign it when you build the form `'company': newFormControl(this.data.company)`. But tell me the exact error message and let's see if I can help out :) – AT82 Apr 07 '17 at 13:18
  • @AJT_82. Lol yeah those string values were to silence the undefined error so I could troubleshoot. I'm going to try your answer when you get home. It looks like it will work. – ctilley79 Apr 07 '17 at 14:00
  • sure thing! Let me know how it goes :) – AT82 Apr 07 '17 at 14:01

2 Answers2

1

First issue is that the call is asynchronous, so initForm is called before data has been retrieved. More info here: How do I return the response from an Observable/http/async call in angular2? So you need to call the method inside the callback.

Tested the code, and so it seems, that when you add the initForm inside the callback it still doesn't work, even though setting the form with *ngIf="data". Still the form gets rendered before the form has been completely built, which throws error.

So I see two possibilities here. Set a boolean value, e.g showForm and do not render the form unless showForm is true. You set the flag showForm as true, after data has been retrieved and after the form has been built.

Other option would be to use setValue (or patchValue) where you enter the data to the fields after retrieving the data. Here's an example for you:

build the form in your OnInit with empty values:

this.payeeEditForm = new FormGroup({
  'company': new FormControl()
  'first_name': new FormControl()
  'last_name': new FormControl()
  'notes': new FormControl()
})

And when retrieved data call patchForm in the callback:

this.subscription = this.payeeService.getPayee(this.id)
   .subscribe(data => {
      this.data = data;
      this.patchForm(); // set the values to the form
   });

and your patchForm-method:

patchForm() {
  this.payeeEditForm.setValue({
    company: this.data.company,
    first_name: this.data.first_name,
    last_name: this.data.last_name,
    notes: this.data.notes
  });
}

This seems to work fine! :) And setting the boolean flag worked too, if you prefer that!

Here's a

Demo

Community
  • 1
  • 1
AT82
  • 71,416
  • 24
  • 140
  • 167
0

The callback that you give to the subscribe method is executed later than the initForm method. Because the http call takes X amount of time.

What you could do is to bind all of the properties to the ([ngModel]) in the inputs.

Stan van Heumen
  • 2,236
  • 2
  • 11
  • 20
  • I'm using reactive forms (data driven). will ng model work? – ctilley79 Apr 06 '17 at 23:12
  • call initForm() after this.data = data; – aimprogman Apr 07 '17 at 06:16
  • @aimprogman. If I call it there I get an error on page load about `initForm()` not being called. It does work after I fire the click event calling `getPayee()`, but since I'm not calling `initForm()` on page load, the form doesn't load presenting the error. – ctilley79 Apr 07 '17 at 13:08