Angular Reactive Forms: Dynamic Select dropdown value not binding - javascript

I have two arrays of data: AssociatedPrincipals (previously saved data) and ReferencePrincipals (static data to populate in dropdown controls). I'm struggling to get the previous value from AssociatedPrincipals to be displayed/selected in a dynamic amount (most examples use a single dropdown) of dropdowns on page load.
I'm not certain how to set up the form (code behind and HTML), especially setting the Select's formControlName. Currently, the static values in each dropdown populate, but I cannot get the selected value to bind properly.
public ngOnInit() {
this.factsForm = this.formbuilder.group({
associatedPrincipals: this.formbuilder.array([]),
referencePrincipals: this.formbuilder.array([])
});
// Data for both of these methods comes from external source...
var responseData = // HTTP source...
// Push retrieved data into form
this.initPrincipals(responseData[0]);
// Push static data into form
this.initStaticData(responseData[1]);
}
public initPrincipals(principals?: IAssociatedPrincipal[]): FormArray {
principals.forEach((principal) => {
this.associatedPrincipals.push(this.createPrincipalFormGroup(principal));
});
}
public initStaticData(response: IReferencePrincipal[]) {
response.forEach((principal) => {
this.referencePrincipals.push(
this.formbuilder.control({
code: principal.code,
canHaveLead: principal.canHaveLead,
isDuplicate: false
}));
});
}
public createPrincipalFormGroup(principal: IAssociatedPrincipal) {
return this.formbuilder.group({
code: principal.code,
canHaveLead: false,
isDuplicate: false
});
}
public get associatedPrincipals(): FormArray {
return this.factsForm.get('associatedPrincipals') as FormArray;
}
public get referencePrincipals(): FormArray {
return this.factsForm.get("referencePrincipals") as FormArray;
}
HTML:
<form novalidate [formGroup]="factsForm">
<div formArrayName="associatedPrincipals">
<div *ngFor="let associatedPrincipal of associatedPrincipals.controls; let i=index;" [formGroupName]="i" >
<select class="form-control create-input"
formControlName="i">
<option value=null disabled selected hidden>--Select--</option>
<option *ngFor="let refPrincipal of referencePrincipals.controls" [ngValue]="refPrincipal">refPrincipal.value.code</option>
</select>
</div>
</div>
</form>
I appreciate any feedback!
EDIT: Added Plunker showing the issue: https://embed.plnkr.co/XMLvFUbuc32EStLylDGO/

Problems in your demo
Based on the demo you provided, There are several problems as listed below:
There is no formControlName assigned to select.
You are binding object to select's option.
For the first problem
Since you are looping through associatedPrincipals to show dropdownlist dynamically. And associatedPrincipals which is a formArray which can consider as below:
associatedPrincipals = {
"0": FormControl,
"1": FormControl
}
So you can simply assign i which is defined at *ngFor expression to formControlName.
<select formControlName="{{i}}" style="margin-top: 10px">
...
</select>
For the second problem
While binding object to option, Angular will compare default value and option's value by object instance by default.
You can set same instance(get from value of referencePrincipals's formControls) to formControl of associatedPrincipals(as #Fetra R.'s answer). But this is not the most convenient way since you have to take some logic to keep the same instance of an object.
Here I would give you another solution which is using compareWith directive designed specifically for your current situation, see docs.
Using compareWith directive, you just need to implement a compareFun to tell angular how to consider two objects(with different instances) as the same.Here yo can include comparing object instance and comparing object fields at the same time.
<select formControlName="{{i}}" style="margin-top: 10px" [compareWith]="compareFun">
<option value=null disabled selected hidden>--Select--</option>
<option *ngFor="let refPrincipal of referencePrincipals.controls"
[ngValue]="refPrincipal.value">{{ refPrincipal.value.code }}</option>
</select>
// tell angular how to compare two objects
compareFn(item1, item2): boolean {
return item1 && item2 ? item1.code === item2.code : item1 === item2;
}
Refer docs and fixed demo to learn detail about it.

You need to pass the exact same reference of the object which populate the select into the selected one to get the selected value.
Here you use a value of all FormControl in referencePrincipals to populate your selectbox, so to get it selected use this object:
public createPrincipalFormControl(principal) {
const selectedFormControl = this.referencePrincipals.controls.find(form => form.value.code === principal.code)
return this.formbuilder.control(selectedFormControl.value);
}
Working plunker. https://plnkr.co/edit/vw3WZ6?p=preview

There are at least 2 problems with your approach.
Your data source here is probably async. Which means you should not do this.initiPrincipals(responseData[0]) immediately after var responseData but instead in callback of whatever method gets you the data or in a subscription to http service, if you get data through Observable.
let subscription = myservice.getmedata.subscribe(data =>
{
//here you should do your initializations with data from server
};
If your data comes from #Input() then the right plase is ngOnChanges.
As Fetra pointed out, regardless of the fact that your previously selected option has exactly the same value as the ones you've prepopulated into select list, in order to set it as selected you need exact reference to the ones you've populated it with. so, something like:
this.formGroup.controls['yourSelectControl'].patchValue(this.yourInitialCollectionOfOptions.find(v => v.propertyByWhichYouWantToCompare == valueFromServer.propertyByWhichYouWantToCompare)

Related

Angular ng-select : selectedItems.map is not a function

When I'm using ng-select in reactive form angular I get this error:
ERROR TypeError: selectedItems.map is not a function
I have 3 select the first two work very well but in this third one I get the error ! to map item i'm using the function (the ng-select is inside *ngFor) :
//for mappinig item :
mapLabelValueBS(objet) {
return objet.map(data => {
return {
id: data,
text: data.name
}
})
}
//this is the one that is causing the problem
<ng-select
[allowClear]="true"
[items]="mapLabelValueBS(filieres)"
placeholder="Filière non sélectionné"
(selected)="selecteFiliere($event)"
formControlName="filiere">
</ng-select>
the result in my page (when I click on the field it doubles itself) :
Without the code is difficult to know, but today I had the same error. The reason was that I determined a default value in the FormControl that had no relation with the array that ng-select demands. When the FormGroup loaded, and this mistaken default was loaded into the ng-select, the error was selectedItems.map is not a function
This error came while I was passing [items]="value" where the value was not an array, so please check if you are not passing non array element to items binding.
You are trying to bind the items of object type. [items] attribute accepts an array. You can trying adding a pipe keyvalue
<ng-select
[allowClear]="true"
[items]="jsonData | keyvalue"
placeholder="Filière non sélectionné"
(selected)="selecteFiliere($event)"
formControlName="filiere">
</ng-select>
few days ago i came across this error if you are binding a list that is filled from backend server be sure to fill the list using concat method like this
this.userService.getLookup().subscribe((res: any) => {
this.apps = this.apps.concat(res.data);
});
I had same problem, because the list of items was undefined sometime in the middle of page preparing, so I added a silly condition to show those select only when the list of items is ready:
<ng-select
*ngIf="selectedItems.map"
[allowClear]="true"
[items]="jsonData | keyvalue"
placeholder="Filière non sélectionné"
(selected)="selecteFiliere($event)"
formControlName="filiere">
</ng-select>

Ember.js - multiple checkboxes - pass values to queryParam

I have a project setup like in the following Twiddle
https://ember-twiddle.com/9b8b42ac659f746370576ed8fde64630
I'm trying to pass the language.code to the language queryParam for each selected language. In the actual project I'm using https://github.com/DockYard/ember-one-way-controls and I've managed to do this with an action bound to the input but on the page refresh the checkboxes values won't stick.
Is there a way to achieve this?
Well, your problem is that you can't bind queryParams to computed properties. So you can't to this in a nice way when using databindings, but when you go for DDAU, one way controls and closure actions its really easy.
By the way, you don't need ember-one-way-controls. Ember can now handle most of this by its own.
So your solution.
First you need to bind your queryParams to an array, because you want to store a list of values:
selectedLanguages: [],
queryParams: ['selectedLanguages'],
Now you need to fire an action when a user clicks a checkbox. This can done by using a simple <input> element with closure actions:
<input type="checkbox" onclick={{action 'toggleLanguage' language}} checked={{language.checked}} />
Now you have an action where you can change that selectedLanguages array. A simple approach could look like this:
actions: {
toggleLanguage(language) {
const selectedLanguages = this.get('selectedLanguages');
const {code} = language;
if(selectedLanguages.includes(code)) {
selectedLanguages.removeObject(code);
} else {
this.get('selectedLanguages').pushObject(code);
}
}
}
Now you have everything you want, just the checkboxes don't get checked after a page reload. But to fix this just use a CP to generate the checked boolean:
languagesWithSelected: Ember.computed('selectedLanguages.[]', 'languages.#each.code', {
get() {
return this.get('languages').map(({code, name}) => ({
code,
name,
checked: this.get('selectedLanguages').includes(code),
}));
}
}),
You can find a working solution in this twiddle.

Array being treated as string by angular with loopback backend

I am new to angular and have been trying to get ng-repeat working, but have been unable to do so. The backend is based on Loopback. Here is my code.
<select>
<option ng-repeat="item in getCityList" value="{{item}}">{{item}}
</option>
</select>
If getCityListis hardcoded as an array, it works e.g. $scope.getCityList = ["Karachi", "Lahore"] and each item forms an individual select option, i.e. Karachi and Lahore
However, if I use a function defined on a Loopback model instead, e.g. self.getCityList = Application.getCityList();, the two city names are not printed individually, but rather as one select option i.e. ["Karachi", "Lahore"]
It seems that Application.getCityList() is outputting the array as string. Is there any way to treat it as an array instead?
Here is the code for Application.getCityList()
Application.getCityList = function (cb) { var areaList = ["Karachi", "Lahore"]; cb(null, areaList); };
Your problem is the line
self.getCityList = Application.getCityList();
getCityList takes a callback method. It does not return the data, but pass it to the callback.
Application.getCityList(function(err,data) {
self.getCityList = data;
});
Should work

Displaying checkbox selected rows with vuejs

I'm starting out with vuejs and a vue grid at https://jsfiddle.net/kc11/7fqgavvq/2/.
I want to display the checked row objects in the:
<pre> {{ selected| json}} </pre>
at the bottom of the table. I've come across Check all checkboxes vuejs with an associated fiddle at https://jsfiddle.net/okv0rgrk/206/ that shows what I mean if you look at the outputted Selected Ids.
To get this working I'll need to add a method to the table component similar to
methods: {
selectAll: function() {
this.selected = [];
for (user in this.users) {
this.selected.push(this.users[user].id);
}
}
in https://jsfiddle.net/okv0rgrk/206/
Can someone explain this function as I am having trouble in particular with what 'this' means in this context.
this refers to your component. So anything inside of your component can be called using this. You can access data with this.users or this.selected, run methods with this.selectAll() or access anything else in your component.
In that fiddle, there is a users attribute on the data, so this.users refers to that array. The function selectAll() empties the this.selected array, then goes through and re-adds every user to the array, this.selected.
Edit -- computed properties
Add this to your component:
computed:{
userCount: function(){
return this.users.length;
}
}
then, anywhere in this component, this.userCount will return the number of users. This variable will update anytime the number of users changes. That is why its a "computed" property - you don't have to update, it just automatically recalculates when it needs to.

angular select box - ng-options custom description

I've got a working angular select box:
<select class="form-control"
ng-model="selectedFeatureTypeFeature"
ng-options="FeatureTypeFeature.ProjectVersionFeatureID as
FeatureTypeFeature.Description for FeatureTypeFeature in FeatureTypeFeatures"></select>
Which I'm trying to modify the 'as' portion in the ng-options, to look like this:
<select class="form-control"
ng-model="selectedFeatureTypeFeature"
ng-options="FeatureTypeFeature.ProjectVersionFeatureID as
(FeatureTypeFeature.CustomDesciption != null? FeatureTypeFeature.CustomDescription : FeatureTypeFeature.Description) for FeatureTypeFeature in FeatureTypeFeatures"></select>
The difference is some FeatureTypeFeatures have a custom description and some do not. I want to display the custom description when its available, and the normal description when its not.
The problem is - it will only display the regular description whether there is custom description or not.
Logic like this really belongs in the controller, rather than the view.
An easy way to create a conditional list is to use the angular.forEach function in your controller. Here are the docs.
Without knowing the context of how your data is structured and your existing controller, I've created a generic solution which you can look through here: Plunker.
Basically you create a new $scope variable for the descriptions (and the id's) using the forEach iterator to conditionally push the description if the customDescription is empty:
$scope.descriptionList = [];
angular.forEach($scope.FeatureType, function(value, key) {
if (value.customDescription !== '')
$scope.descriptionList.push({ id:value.id, description:value.customDescription});
else
$scope.descriptionList.push({ id:value.id, description:value.description});
});
Then use this new $scope variable to populate your <select> form.
I hope this helps.
Solution: created a function in the controller, which returned the correct Description.
Html:
ng-options="FeatureTypeFeature.ProjectVersionFeatureID as (getFeatureTypeFeatureDescription(FeatureTypeFeature)) for FeatureTypeFeature in FeatureTypeFeatures"
Controller:
$scope.getFeatureTypeFeatureDescription = function (featureTypeFeature) {
return featureTypeFeature.CustomDescription || featureTypeFeature.Description;
}

Categories

Resources