0

I have two <select> elements, one for the Districts and the other one for the Cities. I get the districts by calling the function getDistricts() from ngOnInit() function. So far so good.

I don't know how to tell angular to fetch the new cities to the cities' select element. I tried to do like in the code below, but I've got an error:

ORIGINAL EXCEPTION: TypeError: Cannot read property 'cities' of undefined

It happens of course because the selectedDistrict is undefined in the start.


This is an example of a district object. (it includes the cities in it).

{
  "id": 5,
  "name": "A district",
  "cities": [
    {
      "id": 59,
      "name": "City 1"
    },
    {
      "id": 60,
      "name": "City 2"
    }
  ]
},

district.service.ts:

import {Injectable}         from "@angular/core";
import 'rxjs/add/operator/toPromise';

import {District}               from "./district";
import {HttpClientService}  from "../http-client/http-client.service";

@Injectable()
export class DistrictService {

    private districtsUrl = 'districts/all';

    constructor(private httpClient: HttpClientService) {
        this.httpClient = httpClient;
    }

    getDistricts(): Promise<District[]> {
        return this.httpClient.get(this.districtsUrl)
            .toPromise()
            .then(response => response.json().data)
            .catch(this.handleError);
    }

    getDistrict(district: string): Promise<District> {
        return this.getDistricts()[district];
    }

    private handleError(error: any) {
        console.error('An error occurred', error);
        return Promise.reject(error.message || error);
    }
}

view-search.component.ts:

export class ViewSearchComponent implements OnInit {
    districts: district[];
    selectedDistrict: district;

    constructor(private districtService: districtService) {}

    ngOnInit() {
        this.getDistricts();
    }

    getDistricts() {
        return this.districtService.getDistricts().then(districts => this.districts = districts);
    }

    selectDistrict(district: district) {
        this.selectedDistrict = district;
    }
}

view.search.component.html

<select class="search-select">
    <option *ngFor="let district of districts" (click)="selectDistrict(district)">
        {{ district.name }}
    </option>
</select>

<select class="search-select">
    <option *ngFor="let city of selectedDistrict.cities ">
        {{ city.name }}
    </option>
</select>

4 Answers 4

1

Very few people know that Angular2 supports elvis operator (i.e. ?) in templates which is extremely useful for asynchronous data streams. For that, you have to update the template as,

<select class="search-select">
    <option *ngFor="let city of selectedDistrict?.cities ">
        {{ city.name }}
    </option>
</select>
Sign up to request clarification or add additional context in comments.

5 Comments

what does it do exactly?
If I'm not wrong, you wanted to avoid Cannot read property 'cities' of undefined error especially when the data arrives asynchronously after some time. So the elvis operator does not read cities if selectedDistrict object is undefined/null and prevents the error from happening. Is not it solving your problem?
I thought about another solution, I did *ngIf="selectedDistrict" which solved my problem. Your solution looks cleaner. I'll check it out
Yeah. Note that ngIf will remove the DOM first and add it back later which is unnecessary. Not at all ideal if your DOM is huge and complex.
Thank you! your solution is Epic and much more cooler than mine! :P
1

Try to initialize your selectedDistrict: district;

selectedDistrict: district = {};

also for selecting an option I don't think this is a good idea (click)="selectDistrict(district)" because you could theoretically use the keyboard/arrows to select an item

so you should use the onchange event

<select (change)="alert(this.options[this.selectedIndex].text);">

3 Comments

Well now I don't get any error at the start, but I think that something is wrong when I do (click)="selectDistrict(district)" because I added to my html this code: {{ selectedArea | json }} and it was still empty even when I clicked on a district.
I don't know why, but I don't get any alert. What am I missing?
It is, but your answer is problematic because you can't make an empty object, It's not an instance of my District.
0

try to initialize the districts: district[]; array you can do that like:

districts = [];

the problem is that you are trying to access city property of an uninitialized object. you need to make sure that you districts objects are initialized.

for example using new

this.selectedDistrict = new District();

or

district = {  "id": 5,
  "name": "A district",
  "cities": [
    {
      "id": 59,
      "name": "City 1"
    },
    {
      "id": 60,
      "name": "City 2"
    }
  ]}

also in this code

<select class="search-select">
    <option *ngFor="let district of districts" (click)="selectDistrict(district)">
        {{ district.name }}
    </option>
</select>

make sure that selectDistrict is called correctly

1 Comment

This is a problematic approach. I found a solution. I just had to do *ngIf="selectedDistrict".
0

Instead of initializing my selectedDistrict, I just had to add *ngIf="selectedDistrict" to my select element.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.