Daniel Kucal
Daniel Kucal

Reputation: 9232

Angular: set selected value for <select>

I have data set for my <select> loaded asynchronously. I use hot observable, as the data can change in time. The problem is I am unable to set selected value and also Angular does not point to first element by itself (there is no value set until user does it). I'm trying to subscribe to my own observable and... it doesn't work, I wonder why? How can I solve this problem?

PLNKR: https://plnkr.co/edit/uDmTSHq4naYGUjjhEe5Q

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
    </div>
    <select [(ngModel)]="selectedValue">
      <option *ngFor="let value of (values$ | async)"
              [value]="value">{{ value }}
      </option>
    </select>
  `,
})
export class App implements OnInit, OnDestroy {
  public name: string;
  public selectedValue: string = '';
  public values$: Observable<Array<string>> = new Observable(observer => this.observer = observer);
  protected observer: Subscriber<Array<string>>;
  protected subscription: Subscription;

  constructor() {
    this.name = `Angular! v${VERSION.full}`
  }

  ngOnInit() {
    this.subscription = this.values$.subscribe((values) => {
      console.log('never fired...');
      this.selectedValue = values[0];
    });

    setTimeout(() => {
      this.observer.next(['some', 'test', 'data']);
    });
  }

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

Upvotes: 4

Views: 7714

Answers (3)

yurzui
yurzui

Reputation: 214007

You subscribe to your observable twice. Async pipe does it internally after your subscription.

When subscribe method is being executed it executes subscribe function

observer => this.observer = observer

and overrides this.observer property so it will have effect only for async pipe(last subscriber)

I would use share operator to solve it

new Observable(observer => this.observer = observer).share();

Plunker Example

To see why this.observer is overrided just run this code

let myObserver;   

const observable$ = new Rx.Observable(function subscribe(observer) {
  console.log('subscribe function has been called');
  myObserver = observer;
}); 

observable$.subscribe(function next1() { console.log('next1'); });
observable$.subscribe(function next2() { console.log('next2'); });
observable$.subscribe(function next3() { console.log('next3'); });

myObserver.next();

jsbin.com

As i mentioned early async pipe subscribes to observable internally

https://github.com/angular/angular/blob/4.3.x/packages/common/src/pipes/async_pipe.ts#L23

Upvotes: 6

Dan R.
Dan R.

Reputation: 648

You should bind it to ngValue:

  <div>
      <h2>Hello {{name}}</h2>
    </div>
    <select [(ngModel)]="selectedValue">
      <option *ngFor="let value of (values$ | async)"
              [ngValue]="selectedValue">{{ value }}
      </option>
    </select>

Upvotes: 1

Aravind
Aravind

Reputation: 41533

You should be using

[ngValue]="value" 

instead of [value]

Assigned a value as

public selectedValue: string = 'test';

HTML should be changed as

<select [(ngModel)]="selectedValue">
  <option *ngFor="let value of values$ | async"
          [ngValue]="value">{{ value }}
  </option>
</select>

Updated Plunker

Upvotes: 1

Related Questions