简体   繁体   中英

Angular 2: populate FormBuilder with data from http

I get my data from http with rjsx in component (let name it customer ).

Then i'm using inner component in customer:

<customer>
  <customer-form [customer]="customer"></customer-form>
</customer>



<!-- [customer]="customer" // here is data from http -->

and in customer-form i have:

@Input() customer:ICustomer;

complexForm : FormGroup;



constructor(fb: FormBuilder) {

  this.complexForm = fb.group({
    'name': [this.customer['name'], Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(255)])]
  });
}

but i get:

Cannot read property 'name' of undefined
TypeError: Cannot read property 'name' of undefined

if i understood correctly : it's due to the fact that constructor is called, but data isn't fetched yet from http, so customer is empty. But how to fix this?

upd: my http data get:

   getCustomer(id) {
    this.customerService.getCustomer(id)
      .subscribe(
        customer => this.customer = customer,
        error =>  this.errorMessage = <any>error);
  }
  ----


@Injectable()
export class CustomerService {

  private customersUrl = 'api/customer';

  constructor (private http: Http) {}

  getCustomers (): Observable<ICustomer[]> {
    return this.http.get(this.customersUrl)
      .map(this.extractData)
      .catch(this.handleError);
  }

  getCustomer (id): Observable<ICustomer> {
    return this.http.get(this.customersUrl + '/' + id)
      .map(this.extractData)
      .catch(this.handleError);
  }



  private extractData(res: Response) {
    let body = res.json();
    return body || { };
  }


  private handleError (error: Response | any) {
    // In a real world app, we might use a remote logging infrastructure
    let errMsg: string;
    if (error instanceof Response) {
      const body = error.json() || '';
      const err = body.error || JSON.stringify(body);
      errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    console.error(errMsg);
    return Observable.throw(errMsg);
  }

}

as @Bhushan Gadekar stated, you are accessing customer when it has not been initialized.

There are multiple way to handle this correctly :

Using a setter:

@Input("customer") 
set _customer(c:ICustomer){
  this.customer=c;
  this.complexForm.get("name").setValue(c.name,{onlySelf:true});
}
customer:ICustomer;
complexForm : FormGroup;

constructor(fb: FormBuilder) {

  this.complexForm = fb.group({
    'name': [null, Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(255)])]
  });
}

Using an Observable

Here, the customer needs to be an Observable of ICustomer

@Input() customer:Observable<ICustomer>;

complexForm : FormGroup;

constructor(fb: FormBuilder) {
  this.complexForm = fb.group({
    'name': [this.customer['name'], Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(255)])]
  });
}

ngOnInit(){
  this.customer.map(c=>this.complexForm.get("name").setValue(c.name,{onlySelf:true}))
  .subscribe();
}

Mixing both :

@Input("customer") 
set _customer(c:ICustomer){
  this.customer.next(c);
}
customer=New Subject<ICustomer>();
complexForm : FormGroup;

constructor(fb: FormBuilder) {
  this.complexForm = fb.group({
    'name': [null, Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(255)])]
  });
}

ngOnInit(){
  this.customer.map(c=>this.complexForm.get("name").setValue(c.name,{onlySelf:true}))
  .subscribe();
}

Case for multiple properties :

If you don't want to write every form update one by one, and if your form's field names are the same as your Object you can loop over customer properties:

Object.keys(customer).forEach(k=>{
  let control = this.complexForm.get(k);
  if(control)
    control.setValue(customer[k],{onlySelf:true});
});

Note that this code will work only if your form's controls are named the same way as customer's properties are . If not, you may need to make a hash mapping customer properties name to formControls name.

Important point:

Yous should never access inputs from the constructor as they are not populated yet, all inputs should get populated (at least the synchronous ones) just before the ngOnInit hook. Take a look at the Lifecycle hooks documentation

I can see that you are trying to access customer object when it is not populated.

Issue here is that http call takes some time to be resolved.thus, your view is trying to access customer object even when it is undefined.

try this:

<customer *ngIf="customer">
  <customer-form [customer]="customer"></customer-form>
</customer>

Though the way you are accessing name property is also not good. Best approach is to create a customer model and use your property as className.propertyName

Hoe this helps.

代替ngOnInit,尝试ngAfterViewInit

不要在component.ts中使用subscribe在component.html中添加异步管道,例如: <customer-form [customer]="customer | async"></customer-form>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM