ViewModel class in Typescript I am trying to refactor a ViewModel class because it has two methods that are really similar called updateRegions and updateTowns and even another one may end up being made called updateCountries. I basically copy-paste the method and change the variable names. How can this be refactored with maybe an abstract class that has an abstract updateArea function?

class SearchFilterViewModel {
        _regionsList = [];
        _townsList = [];
        _regions = ko.observableArray();
        _towns = ko.observableArray();
        selectedRegion: KnockoutObservable&lt;string&gt; = ko.observable('');
        selectedTown: KnockoutObservable&lt;string&gt; = ko.observable('');
        selectedCategories: KnockoutObservableArray&lt;string&gt; = ko.observableArray([]);

        constructor() {
            this.updateRegions("2186224");
            this.selectedRegion.subscribe(this.updateTowns.bind(this));
        }

        updateRegions(geonameId) {
            var self = this;
            self._regionsList = [];
            $.ajax({
                url: `http://api.geonames.org/children?geonameId=${geonameId}&amp;username=elion`
            }).then(function(allRegionsXML) {
                var allRegionsJSON = xml2json(allRegionsXML);
                var allRegions = JSON.parse(allRegionsJSON);
                if(allRegions.geonames.length) {
                    for (var index = 1; index &lt; allRegions.geonames.length - 1; index++) {
                    self._regionsList.push(allRegions.geonames[index].geoname);
                    }
                } else {
                    if(allRegions.geonames) {
                        self._regionsList.push(allRegions.geonames.geoname);
                    }
                }
                self._regions(self._regionsList);
            });
        }

        updateTowns(geonameId) {
            var self = this;
            self._townsList = [];
            $.ajax({
                url: `http://api.geonames.org/children?geonameId=${geonameId}&amp;username=elion`
            }).then(function(allTownsXML) {
                var allTownsJSON = xml2json(allTownsXML);
                var allTowns = JSON.parse(allTownsJSON);
                if(allTowns.geonames.length) {
                    for (var index = 1; index &lt; allTowns.geonames.length - 1; index++) {
                    self._townsList.push(allTowns.geonames[index].geoname);
                    }
                } else {
                    if(allTowns.geonames) {
                        self._townsList.push(allTowns.geonames.geoname);
                    }
                }
                self._towns(self._townsList);
            });
        }
    }
I have managed to get it down to this. It had been a while since I had encountered this scenario so I had to rack my brain for a bit and do some research. I basically followed this principle. Let me know if you have any suggestions for further improvements. Cheers.

interface iUpdateArea {
    _areasList: Array&lt;string&gt;;
    _areas: KnockoutObservableArray&lt;string&gt;;
    _selectedArea: KnockoutObservable&lt;string&gt;;
    updateSubArea(geonameId: string);
}

class Area implements iUpdateArea  {
    _areasList = [];
    _areas = ko.observableArray([]);
    _selectedArea = ko.observable('');

    updateSubArea(geonameId){
        var self = this;
        self._areasList = [];
        $.ajax({
            url: `http://api.geonames.org/children?geonameId=${geonameId}&amp;username=elion`
        }).then(function(allRegionsXML) {
            var allRegionsJSON = xml2json(allRegionsXML);
            var allRegions = JSON.parse(allRegionsJSON);
            if(allRegions.geonames.length) {
                for (var index = 1; index &lt; allRegions.geonames.length - 1; index++) {
                self._areasList.push(allRegions.geonames[index].geoname);
                }
            } else {
                if(allRegions.geonames) {
                    self._areasList.push(allRegions.geonames.geoname);
                }
            }
            self._areas(self._areasList);
        });
    }
}

class SearchFilterViewModel {
    _region: Area;
    _town: Area;

    constructor() {
        this._region = new Area();
        this._town = new Area();
        this._region.updateSubArea("2186224");
        this._region._selectedArea.subscribe(this._town.updateSubArea.bind(this._region));
    }
}

$(document).ready(function() {
    var _searchFilterViewModel: SearchFilterViewModel = new SearchFilterViewModel();
    var _searchFilterForm = $("#find-vegan-products-page").find("form")[0];
    ko.applyBindings(_searchFilterViewModel, _searchFilterForm);
    $('.select-multiple').each(function(i, obj) {
        //obj[i] gets each element inside the div
        $(this).multiselect({
            noneSelectedText: "CATEGORIES"
        });
    })
});