As Amit Hadary mentioned, you can use sort()
, and it would be good to modify the array before sending it to the select component, in order to be able to use the select and in other parts to be more generic.
Component parent:
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
countries: Country[] = [
{ name: "Moldova", phoneCode: 373 },
{ name: "Romania", phoneCode: 40 },
{ name: "Belgium", phoneCode: 33 },
{ name: "France", phoneCode: 33 }
];
sortedCountries: Country[];
constructor() {
const popularCountries = new Map<Country["name"], number>([
["Belgium", 1],
["France", 2]
]);
this.sortedCountries = this.countries.sort(
({ name: a }, { name: b }) => (popularCountries.get(b) || 0) - (popularCountries.get(a) || 0),
);
}
template:
<app-select [countries]="sortedCountries"></app-select>
component child:
@Component({
selector: "app-select",
templateUrl: "./select.component.html",
styleUrls: ["./select.component.css"]
})
export class SelectComponent {
@Input() countries: any[];
}
template:
<select>
<option *ngFor="let country of countries">
<!-- A best practice in html is not to use js concatenations -->
{{ country.name }} (+{{ country.phoneCode }})
</option>
</select>
example on stackblitz
You don’t need to create a new array of the popular countries in order to accomplish it.
What you can do is:
@Input()
countries: Country[];
sortedCountries: Country[];
ngOnInit() {
this.sortedCountries = this.countries.sort((countryA, countryB) => {
if(['Albania', 'China', 'Italy'].includes(countryA.name)) return 1;
else return 0;
}
Then in your template just use sortedCountries
.
Check out Array.prototype.sort()
- You’ll first need to determine your popular countries using which every strategy fits best. Lets say they are in an array called
popular
.
- Next you’ll concatenate the popular countries with all the countries.
popular.concat(countries)
.
- You then need to remove the duplicates using uniqBy.
- As you’ve mentioned, you want to sort according to name, thus you can use sortBy.
TS
let entries = popular.concat(countries);
entries = _.uniqBy(entries, 'name');
entries = _.sortBy(entries, 'name');
HTML
<option *ngFor="let country of entries" [value]="country.phoneCode">
{{ country.name + ' (+' + country.phoneCode + ')'}} <!--Country-->
</option>
I ended up doing something a little more simpler by creating two arrays and filtering them based on them having the desired countries and not having the desired countries.
otherCountries: Country[] = new Array<Country>();
popularCountries: Country[] = new Array<Country>();;
ngOnInit() {
this.popularCountries = this.countries.filter(f => f.name === "United Kingdom" || f.name === "United States");
this.otherCountries = this.countries.filter(f => f.name !== "United Kingdom" && f.name !== "United States");
}
I’m working in Angular and I currently have a dropdown list of countries and their corresponding country telephone codes.
I’m looking to place four specific countries at the top of the dropdown list (the most popular options). I’m trying to create a new array and filter this based on naming the countries specifically within an NgOnInit.
Thanks for your help in advance.
component.html
<select class="block appearance-none text-gray-600 w-full bg-white border hover:border-gray-500 px-3 py-3 pr-8 rounded shadow-sm leading-tight focus:outline-none focus:shadow-outline"
[(ngModel)]="value.code" name="code" (change)="modelChange($event)">
// ISSUE HERE
<option *ngFor="let country of countries" [value]="country.phoneCode">
{{ country.name + ' (+' + country.phoneCode + ')'}} <!--Country-->
</option>
// THIS SECTION IS FINE
<option *ngFor="let country of countries" [value]="country.phoneCode">
{{ country.name + ' (+' + country.phoneCode + ')'}}
</option>
</select>
component.ts
import { Component, Input, OnInit, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, FormControl, Validator } from '@angular/forms';
import { Country, Telephone } from 'models';
@Component({
selector: '',
templateUrl: '.component.html',
providers: [
{
provide: NG_VALIDATORS,
useExisting: TPComponent ,
multi: true
},
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => TPComponent ),
}
]
})
export class TPComponent implements ControlValueAccessor, Validator, OnInit {
@Input()
countries: Country[];
@Input()
required: boolean = true;
allCountries: Country[] = [];
popularCountries: any;
private _value: Telephone;
set value(x: Telephone) {
if (this._value !== x) {
this._value = x;
this.onChange(x);
this.onValidationChange();
}
}
get value() {
if (!(this._value)) {
return new Telephone();
}
return this._value;
}
// ISSUE HERE
ngOnInit() {
this.allCountries = new Array<Country>();
this.popularCountries = this.allCountries.filter(f => f.name === "Albania");
}
validate({ value }: FormControl) {
if (value) {
if (this.required) {
if (!value.code) {
return {
required: true,
phoneNumberDialCodeRequired: true,
}
}
if (!value.number) {
return {
required: true,
phoneNumberRequired: true,
}
}
if (!value.type) {
return {
required: true,
phoneNumberTypeRequired: true,
}
}
}
if (value.number && value.number.length < 7) {
return {
phoneNumberMinLengthRequired: true,
}
}
}
return null;
}
registerOnValidatorChange?(fn: () => void): void {
this.onValidationChange = fn;
}
modelChange(event: any) {
this.onValidationChange();
}
onChange: any = () => { };
onTouch: any = () => { };
onValidationChange: any = () => { };
writeValue(obj: any): void {
this.value = obj;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
}
It’s better that you can create a stackbliz demo for that.
Thanks for the comprehensive answer. I ended up doing something a little more simpler, which I was aiming for originally