skip to Main Content

I have the following in my component.ts file:

import {Component, OnInit} from '@angular/core';
import {IBook} from "../../models/book.model";
import {ActivatedRoute} from "@angular/router";
import {BooksService} from "../../services/books.service";
import {BookGenre} from "../../enums/book-genre";

@Component({
  selector: 'bookstore-edit-book',
  templateUrl: './edit-book.component.html',
  styleUrl: './edit-book.component.css'
})
export class EditBookComponent implements OnInit{
  genreIds: number[] = [];
  bookGenres: typeof BookGenre = BookGenre;
  book: IBook = null!;
  selectedGenre: number = 0;

  constructor(
    private booksService: BooksService,
    private route: ActivatedRoute) {
    this.genreIds = Object.values(BookGenre)
      .filter(value => !isNaN(Number(value)))
      .map(Number);
  }

  ngOnInit() {
    this.route.params.subscribe((params) => {
      this.booksService.getBook(params['id']).subscribe(book => {
        this.book = book;
        this.selectedGenre = book.genre;
      })
    })
  }
}

And this in my HTML template:

<div class="form-group">
  <label for="genre">Genre</label>
  <select id="genre" [(ngModel)]="selectedGenre">
    <option *ngFor="let genreId of genreIds"
            [ngValue]="genreId">{{bookGenres[genreId]}}</option>
  </select>
</div>

When I load the page the select dropdown contains all the correct enum values as below:

enter image description here

However, when the book details are loaded, the dropdown is never updated, it is blank (i.e. nothing selected). Shouldn’t this update when the selectedGenre value is changed from the subscription to the getBook observable? The other book details are all updated fine except this?

2

Answers


  1. Chosen as BEST ANSWER

    I've since found out that [(ngModel)] also requires an HTML name attribute to be present, so the HTML should have read like this:

    <div class="form-group">
      <label for="genre">Genre</label>
      <select id="genre" name="genre" [(ngModel)]="selectedGenre">
        <option *ngFor="let genreId of genreIds"
                [ngValue]="genreId">{{bookGenres[genreId]}}</option>
      </select>
    </div>
    

    This is now working properly and binding is working both ways.


  2. We can use [value] instead of [ngValue] since it generally has this problem!

    When you need the entire object, use [ngValue] when you need a string or number use [value]! Overall just stick to [value] and you will be fine!

    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    import { Component, OnInit } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    import { ActivatedRoute } from '@angular/router';
    import { CommonModule } from '@angular/common';
    
    export enum BookGenre {
      ASD,
      ZXA,
      QWERT,
      QWERTY,
    }
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [FormsModule, CommonModule],
      template: `
        <div class="form-group">
          <label for="genre">Genre</label>
          <select id="genre" [(ngModel)]="selectedGenre">
            <option *ngFor="let genreId of genreIds"
                    [value]="genreId">{{bookGenres[genreId]}}</option>
          </select>
        </div>
      `,
    })
    export class App {
      genreIds: number[] = [];
      bookGenres: typeof BookGenre = BookGenre;
      book: any = null!;
      selectedGenre: number = 0;
    
      constructor() {
        this.genreIds = Object.values(BookGenre)
          .filter((value) => !isNaN(Number(value)))
          .map(Number);
      }
    
      ngOnInit() {
        // simulates an API call!
        setTimeout(() => {
          this.selectedGenre = 1;
        }, 5000);
      }
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search