Angular 5 - Observable - set first value of Observ

2019-06-08 18:26发布

I do have an Observable of Contacts in my onInit method and I also will set the first contact of the Observable into my form. Has anyone a hint how to do this?

ngOnInit() { 
    let contactsObservable: Observable<Contact[]> = 
    this.contactService.getAllContacts();
    contactsObservable.subscribe(contacts => {
        this.contacts = contacts; 
    });

    this.form = this.fb.group({ 
                // this.contacts[0] does not work
      contact: [this.contacts[0], [Validators.required]] // how to get contact[0] into form for presetting
    });
}

5条回答
冷血范
2楼-- · 2019-06-08 19:16

Ideally, the form would be in a child component from the one where you get the data. Then you could use the async pipe to pass the contactsObservable directly to the form, and use the values synchronously.

However, in order to do it all in the same OnInit hook, the best way would be to provide a startWith value to your contactsObservable. This will only be viable if you know what the first value will be in every case, and if you know that already, you may as well just instantiate the form control with a valid starting value:

this.form = this.fb.group({ 
  contact: [[ // some value ], [Validators.required]]
});

You could initialize the form in the subscription as Roland mentioned in the comments, but this would reinitialize your form every time the observable emits a value. Similarly, setting the value of the form control imperatively from within a subscription runs the risk of overwriting user-entered input or other funky behavior depending on how your service works.

Examples of this approach would look something like

contactsObservable.subscribe(contacts => {
  this.form = this.fb.group({
    contacts: [contacts, [...Validators]]
  })
});

Or

this.form = this.fb.group({
  contacts: [[], [...Validators]]
});
contactsObservable.subscribe(contacts => {
  this.form.get('contacts').setValue(contacts);
});

Note you could use setValue or patchValue depending on what you need. But this approach is still faulty because it attempts to ignore the asynchronous nature of the data. And it will overwrite whatever is currently in the input, so if the data is updated while the user is typing, their input will be cleared and replaced, which could be frustrating. Plus you will need to handle clean up of the subscription.


The main reason this pattern doesn't work as expected is that you're treating the asynchronous value contacts as though it is synchronous. Just by unpacking it in the component and saving the value to a variable doesn't remove the asynchronous nature of the values arriving through the Observable. Check out this resource for more info on that.

In my opinion, your best bet is to extract the form into a separate component and take advantage of Angular's async pipe to pass your asynchronous data in.

查看更多
Luminary・发光体
3楼-- · 2019-06-08 19:18

You get contacts async. Value can be put when Observable return value.

ngOnInit() { 
   this.form = this.fb.group({ 
     contact: ['', [Validators.required]] 
   });
   let contactsObservable: Observable<Contact[]> = 
   this.contactService.getAllContacts();
   contactsObservable.subscribe(contacts => {
       this.contacts = contacts; 
       this.form.controls['contact'].setValue(this.contacts[0])
   });
}
查看更多
劳资没心,怎么记你
4楼-- · 2019-06-08 19:20

This worked for me Angular 6.x:

 import {FormBuilder, FormGroup, Validators} from '@angular/forms';
 @Input() artist: Artist;
 artistForm: FormGroup;
 constructor(private route: ActivatedRoute, private artistService: ArtistService, private location: Location, private fb: FormBuilder)  { this.createForm(); }


 ngOnInit() {
 this.getArtist();
}

   createForm(){
this.artistForm = this.fb.group({
  ArtistName: ['', Validators.required]

 });
}


   getArtist(): void{
const id = +this.route.snapshot.paramMap.get('id');
this.artistService.getArtist(id).pipe(tap(artist => this.artistForm.setValue({ArtistName: artist.ArtistName}))).subscribe(artist => {
  artist = this.artist;
  });

}

Now the html:

   <h2>Artist Information</h2>
  <form [formGroup]="artistForm">
  <div class="form-group">
  <label class="center-block">Artist Name:

    <input class="form-control" formControlName="ArtistName">
  </label>
</div>

查看更多
Rolldiameter
5楼-- · 2019-06-08 19:22

You can use the Form patchValue to set the form, as vincecampanale pointed out better use Smart/Dumb Components pattern with observable then in your component you can set

  @Input() set contactsList(contacts: any[]) {
    if (contacts) {
      this.form.patchValue({
        contact: contacts[0]
      });
    }
  }
查看更多
爷、活的狠高调
6楼-- · 2019-06-08 19:24

Why don't you simply put the initialization of the form in a subscribe()?

ngOnInit() {
    // ...
    // .take(2) to ensure that it will run only for the first value:
    contactsObservable.take(1).subscribe(contacts => {
        this.form = this.fb.group({contact: [this.contacts[0], Validators.required]];
    });
    // ...
}

Or you can initialize your form without contact, and add it later to the FormGroup.

This will by asyncronous, but you can set an *ngIf in your template like this:

<form *ngIf="form" ...>...</form>
查看更多
登录 后发表回答