Select first element in dropdown in Angular 7 - javascript

I am getting content of dropdown list as a Map<number, string>. When I get the map, it is received sorted according to keys in ascending order.
While showing it in html, I am setting pipe keyvalue and provided a function to sort the items in ascending order of values.
Now, I am trying to select first element of this dropdown, but unable to do so.
I have tried jQuery method to select the first element.
I have tried to set ngModel of the select box to first value of the map, but it sets the value to the first value received in the Map which is sorted by key.
My HTML:
<select class="form-control" id="empId" [(ngModel)]="empId" [disabled]="!isEditable">
<option *ngFor="let emp of empNames | keyvalue:descOrder" [value]="emp.key">{{ emp.value }}</option>
</select>
My ts file:
this.commonService.getEmployeeList())
.subscribe((response) => {
this.empNames = response;
this.empId = this.empNames.keys().next().value;
});
data I am sending from server is:
{id:1,name: "Tim"},
{id:6,name: "Martha"},
{id:5,name: "Alex"},
{id:8,name: "Stacy"}
data I am receiving on screen is like:
Alex
Martha
Stacy
Tim
with Tim pre-selected
what I need is Alex should be pre-selected.

Then set the empId before subscribing.
this.empId = 5;
this.commonService.getEmployeeList())
.subscribe((response) => {
this.empNames = response;
});
Of course you might want another logic based on some kind of order. You can never be sure how the data are going to be received.
In this case you need to send the order from your api and filter by order.

Working Demo
<option *ngFor="let emp of empNames | keyvalue:descOrder" [value]="emp.key" [selected]="emp.id === 1">{{ emp.value }}</option>
you can use selected attribute like above

I would highly recommand you to use Angular's reactive forms! And set the select's value to the one you want, when you recieve your data. Don't use ngModel as it is deprecated and should have been removed by Angular 7 (Or will be soon). Check this

The best way to pre select an option is to use ngModel as you tried. Your list is sorted by keys so what you want is not to select the first item, yes it's the first but in other order so or you change the order in code or you search for the item you want to select and stores it to set on model.
I would suggest some changes that should improve the code and fix your problem.
<select class="form-control" id="empId" [(ngModel)]="currentEmployer" [disabled]="!isEditable">
<option *ngFor="let emp of employers$ | async" [value]="emp">{{ emp.value }}</option>
</select>
And order your list in a pipe with the function you prefer.
public currentEmployer: Employer = null;
private sortByNameAscending(e1, e2) {
return e1.name > e2.name ? 1 : 0;
}
this.employers$ = this.commonService.getEmployeeList().pipe(
switchMap(employers => {
const sortedList = employers.sort(this.sortByNameAscending);
if (sortedList.length > 0) {
this.currentEmployer = sortedList[0];
}
return sortedList;
})
);

Related

$set slowing down performance in Vue

Below is my code
<div v-for="namespace in chosenNamespaces" v-bind:key="namespace.id">
<!-- Select the Parameter-->
<select #change="updateParameter($event, namespace.id)" v-model="currParameterValues[namespace.id]">
<option v-for="parameter in getParametersForNamespace(namespace.id)">{{parameter}}</option>
</select>
<!-- Select Property -->
<select #change="updatePropertyType($event, namespace.id)" v-model="currPropertyValues[namespace.id]">
<option v-for="property in getPropertiesForNamespace(namespace.id)">{{property}}</option>
</select>
<!-- Select Item -->
<select v-model="currItemValues[namespace.id]">
<option v-for="item in getItemsForNamespace(namespace.id)">{{item}}</option>
</select>
</div>
methods: {
updateParameter(data, id){
....
this.$set(currParameterValues, id, data,target.value)
this.$set(currPropertyValues, id, someValue)
}
updatePropertyType(data, id){
...
this.$set(currPropertyValues, someThing, val)
}
}
So I have many div which loops over the list of chosenNamespaces array and creates a set of selects. Now In that, I want to change the value of the second select i.e Select for Property when I change the value of Select paramater for that corresponding namespace using updateParameter method. I do that by using $set to update the array currPropertyValues. But I observe whenever I change the parameter option it take a while(4-5 secs) to process since maybe Vue takes time to react to the change in property array value. If I simply remove $set updateParameter it responds immediately. How can I solve this?
Edit
Here I have replicated on fiddle, when I change a value in dropdown it takes time to reflect: fiddle
This happens because of using the v-model array indexes like object keys, which creates huge arrays. For example, doing the following creates an array with 152,395,893 items, which will be very slow to work with in a template:
const arr = [];
arr[152395893] = '...';
With an array, this number is not a key's name, but a numerical index. What you want is an object. This creates just 1 item:
const obj = {};
obj[152395893] = '...';
Change these both to objects:
currParameterValues: {},
currOperatorValues: {}
The adjusted Fiddle

Why select is showing first of it's element when it's binded to model

I have a select in angular6 in which option is iterating over an array.
That array has one pipe which dynamically changes array value.
The problem is that when I have one selected value in model and array get changed and that value is not available in that array on which it iterates. Then select by default showing the first element but it should show empty select.
Kindly suggest me the best way to do it.
<select
(change)="handleOfferChange()"
class="alc-form-control"
name="offer"
id="offer"
[(ngModel)]="detailCopy.allowedOffers">
<option
*ngFor="let offer of providerMaster | active: activeOnly"
[ngValue]="[offer.csaid]"
>{{ offer.csaid }}</option
>
</select>
You can manually reset the ngModel, to do this you have to pass ngModel to you pipe as an argument and in your pipe when your array changes, reset the ngModel to empty value (Shown below)
//yourTemplate.html
<select [(ngModel)]="detailCopy.allowedOffers" #mySelectModel="ngModel">
<option *ngFor="let offer of providerMaster | active: activeOnly : mySelectModel" [ngValue]="[offer.csaid]" >{{ offer.csaid }}</option>
</select>
//yourPipe.ts
......
import { NgModel } from '#angular/forms';
transform(arr: any[], mySelectModel: NgModel): any {
if (your logic and conditions) {
mySelectModel.reset();
return arr;
}
}

Get last clicked element in multiple select Angular

Im trying to get the last clicked option in a multiple select, no matters if the option is selected or unselected.
The problem is that the select is not via template, is mounted dinamically via TS.
I've tried adding vanilla JS event listeners at the creation of the options but doesn't works. Actually i can get all the selected elements, but i lost the unselected option and i can't get exactly the new selected one.
My HTML
<tr *ngFor="let communityLine of communityLines">
<td>{{communityLine.name}}</td>
<td>{{communityLine.instrument.name}}</td>
<td>{{communityLine.param.name}}</td>
<td>{{communityLine.contextSource.name}}</td>
<td>{{communityLine.sampleType.name}}</td>
<td>{{communityLine.value}}</td>
<td>
<select multiple [id] ="communityLine.apiKey" (change)="eventGetChange(communityLine, $event)" [(ngModel)]="nodeKey">
</select>
</td>
</tr>
My TS function
private eventGetChange(commLineKey, event) {
console.log(this.nodeKey);
console.log(commLineKey);
console.log(event.target.value)
My TS method to mount the select, is a bit complicated because i need to show all nodes (stored in this.allNodes var) but select the nodes that are in other array (nodesInRelation var).
private mountSelect(nodesInRelation: Node[], lineApiKey: String): void {
let select = <HTMLSelectElement>document.getElementById(lineApiKey);
let copy = this.allNodes;
for (let node of nodesInRelation) {
copy.forEach((item, index) => {
if (item.name === node.name) copy.splice(index, 1);
});
}
for (let node of nodesInRelation) {
let newoption = new Option(node.name, node.apiKey, null, true);
select.add(newoption);
}
for (let node of copy) {
let newoption = new Option(node.name, node.apiKey, null, false);
select.add(newoption);
}
M.updateTextFields();
M.AutoInit();
}
In eventGetChange function first console.log i get all the current selected values, in the second i get the key and is okey and in the third i get only the first selected element in the box.
I just want the last clicked, selected or unselected.
Thanks you.
You seem to be ditching Angular and opting for direct DOM manipulation for no apparent reason. Angular is perfectly capable of populating options list programmatically. It could look something like
<td>
<select multiple [id] ="communityLine.apiKey" [(ngModel)]="nodeKey">
<option *ngFor="let option of optionList; let i = index"
[value]="option" (click)="eventGetChange(option)">{{ option }}
</option>
</select>
</td>
optionList: any[];
private mountSelect(nodesInRelation: Node[], lineApiKey: String): void {
// populate optionList here
}
private eventGetChange(commLineKey) {
// the clicked option is available here
}
If this.nodeKey as you say stores an array with all the current selected values you can simply do this:
this.nodeKey[this.nodeKey.length - 1]
It will give you the las value in the nodeKey array

Preventing filter while editing table content in angularjs

Referring to this question
I am able to successfully apply filter for my table. Now I want to change the way this filter is working. Here is the situation:
I am applying Mapped filter. It filtered out all mapped variables for me. Now I will change the value of one of the filtered variables, as soon as I delete the complete value, the variable is moved to Unmapped list and User is not able to change the variable. User now need to change the filter to either All or UnMapped filter to edit that variable.
Same in the case of Unmapped filter. Select Unmapped filter, as soon as I try entering value, the variable disappears and moved to 'Mapped' list.
What I need to do is to apply filter only when I select filter from drop down using ng-change and when I try to edit some variable the filter should not work.
Here is the code:
For Filter :
$scope.filterOpt = 'All';
$scope.emptyOrNull = function (variable) {
if ($scope.filterOpt == "All")
return true;
if ($scope.filterOpt == "Mapped")
return variable.Name && variable.Variable
if ($scope.filterOpt == "UnMapped")
return !(variable.Variable) ;
}
HTML :
<select class="selectpicker form-control" ng-model="filterOpt" ng-change="emptyOrNull()">
<option value="All">All</option>
<option value="Mapped">Mapped</option>
<option value="UnMapped">Un-Mapped</option>
</select>
and Table:
<tr ng-repeat="var in Mappings | filter: searchVariable | filter : emptyOrNull">
<td>{{var.Name}}</td>
<td>
<div><input-dropdown name="fqn" ng-model="var.Variable" list="variables" ng-disabled="var.IsTrue"></input-dropdown></div>
</td>
</tr>
UI :
In above picture when I select Mapped from filter and try to change/delete Value1 it should not disappear.
Please help.
Basically I don't understand why do you want | filter : emptyOrNull, when you want to update UI only if you change the dropdown value.
Why don't you only update array($scope.Mappings). In this array you can only push values you want to display.
Remove filter and update your dropdown ng-change function like this
Here origArray is your original array, I am just changing scope variables
$scope.emptyOrNull = function (variable) {
$scope.Mappings = [];
if ($scope.filterOpt == "All") {
$scope.Mappings = angular.copy(origArray);
} else {
for (var i = 0; i < origArray.length; i++) {
if ($scope.filterOpt == "Mapped") {
if (origArray[i].Name && origArray[i].Variable) {
$scope.Mappings.push(origArray[i]);
}
}
if ($scope.filterOpt == "UnMapped") {
if (!origArray[i].Variable) {
$scope.Mappings.push(origArray[i]);
}
}
}
}
}
Don't use filter for this kind of requirements, it reduces performance.
What you can do is add a conditional filter. That means apply filter only when you want it to be and don't apply when you don't want.
you can check when input is focused with something like and toggle a variable to disable or enable filter
<input-dropdown name="fqn" ng-model="var.Variable" ng-focus="disable=true" list="variables" ng-disabled="var.IsTrue"></input-dropdown>
And to do conditional filtering use
<tr ng-repeat="var in Mappings | filter : (disable ? '' : emptyOrNull)">
And then you can update the disable to true on changing mapped/unmapped dropdown.
Hope this helps

Dropdown list - model binding by value - angular 2

I have a page which allows a user to update the colour of a car. There are two api calls, one to bring back the car json object and one to fill a drop down list of colours.
My issue is that Angular 2 appears to do model binding via reference and not value. This means that although the colour 'green' might be set on the car, the color 'green' will not be selected in the drop down list even when it matches as that object has come from a different api call.
Here the select list is bound to the 'colour' property of car.
<div>
<label>Colour</label>
<div>
<select [(ngModel)]="car.colour">
<option *ngFor="let x of colours" [ngValue]="x">{{x.name}}</option>
</select>
</div>
</div>
When I set up the model in the back-end, if I set the color of the car to have the same value object (in this case green), the drop down is not selected. However when I set it using the same instance from the list of values used to bind the list it is selected as expected.
ngOnInit(): void {
this.colours = Array<Colour>();
this.colours.push(new Colour(-1, 'Please select'));
this.colours.push(new Colour(1, 'Green'));
this.colours.push(new Colour(2, 'Pink'));
this.car = new Car();
//this.car.colour = this.colours[1]; // Works
this.car.colour = new Colour(1, 'Green'); // Fails
}
Here is a plunker showing the issue. Simply switch between these to lines to illustrate the issue.
this.car.colour = this.colours[1]; // Works
this.car.colour = new Colour(1, 'Green'); // Fails
https://plnkr.co/edit/m3xBf8Hq9MnKiaZrjAaI?p=preview
How can I get angular to compare objects by value not reference when binding in this way?
Regards
Steve
Update
I solved in in my use case by setting the models 'superPower' property to the matching item in the list used to populate the dropdown list.
setupUpdate(id: number): void {
this.pageMode = PageMode.Update;
this.submitButtonText = "Update";
this.httpService.get<Hero>(this.appSettings.ApiEndPoint + 'hero/' + this.routeParams.get('id')).subscribe(response => {
this.hero = response;
this.httpService.get<SuperPower[]>(this.appSettings.ApiEndPoint + 'superPower/').subscribe(response => {
this.superPowers = response;
this.hero.superPower = this.superPowers.filter(x => x.id == this.hero.superPower.id)[0];
});
});
}
That's as designed. Angular2 only compares the object reference, not properties of an object.
You can bind to primitive values then compairson works as you expect
<select [(ngModel)]="car.colour.name">
<option *ngFor="let x of colours" [value]="x.name">{{x.name}}</option>
</select>
assuming that Color has a property name that contains the string Green.
You can also do the compairson yourself by looking up car.colour in colours and setting car.colour to the Colour instance from colours that represents the same colour.
You can use the following
<select [(ngModel)]="car.colour.name" (ngModelChange)="someFunction($event)" >
<option *ngFor="let x of colours" [value]="x.name">{{x.name}}</option>
</select>
When selected value would be updated, you will handle this in someFunction

Categories