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

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>

Related

Vue-multiselect: How to convert object to array for use in options prop?

I am using vue-multiselect like so:
<multiselect
id="customer_last_name_input"
v-model="value"
:options="activeUserProfiles"
label="lastname"
placeholder="Select or search for an existing customer"
track-by="uid"
:close-on-select="true"
#select="onSelect"
#remove="onRemove"
:loading="isLoading"
:custom-label="customerSelectName"
aria-describedby="searchHelpBlock"
selectLabel=""
>
...that grabs the list of active customers from an Array and then makes them available in a nice select menu.
This works good. However, I need to add another option from another resource (called customerNone) to the options prop and but the data is returned as an Object like so:
{"uid":1,"lastname":"None Given","firstname":"User","email":null,"phone":null...blah}
The vue-multiselect docs state that the :option prop MUST be an Array.
Question: What is the best way for me to handle this in the vue-multiselect component? Here's my attempt to help explain what I am trying to do (not sure if this is the best way to handle it). Unfortunately, my attempt causes a console error (see below):
I am passing a prop down called noCustomer which, if is true, I need to use customerNone profile on :options:
<multiselect
:options="noCustomer ? customerNone : getActiveUserProfiles"
>
here's the error:
Invalid prop: type check failed for prop "options". Expected Array, got Object
Is there a way I can convert the customerNone object to an array of object? Thanks!
You could wrap the customerNone object in brackets at the time that you pass it to the <multiselect> like [customerNone].
This syntax creates a new array on the fly, having 1 element that is the object variable:
<multiselect
:options="noCustomer ? [customerNone] : getActiveUserProfiles"
>
Update for comments
In order to auto-select the generic option when it's available, use a watch on the noCustomer prop to set value whenever noCustomer === true:
watch: {
noCustomer(newValue, oldValue) {
if(newValue) { // Checking that `noCustomer === true`
this.value = this.customerNone;
}
}
}

InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe', cannot modify HTML

I am working to populate a dropdown on an application using Angular and ngrx. The app uses a dropdown component which is used across the entire application for dropdowns, so I cannot modify it.
The HTML from that component which is most important for my question is as follows:
<option *ngFor="let option of options" [value]="stringifyOption(option.value)" [selected]="option.value === (selectedValue | async)">
{{option.text}}
</option>
I am getting my dropdown options, which is an array of objects (dateOptions), from the store. Each object has a property value (week) which I am getting and setting as the options for the dropdown.
this.weekDropdownOptions = dateOptions.map(weekObj => weekObj.week);
Logging weekDropdownOptions in the console returns an array of the options like I was expecting, like this:
["04/01 - 04/07", "04/08 - 04/14"]
My HTML has the selector for the dropdown and sets [options]="weekDropdownOptions"
I can't seem to get past this error:
InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe'.
It is pointing towards the dropdown component, however I can't modify the HTML. What can I do in my code to make this work?
the async pipe expects an Observable and not an Object. it would be interesting to see how exactly selectedValue is populated and why it's used with the async pipe.
here is an example on the difference in stackblitz https://stackblitz.com/edit/angular-vv7pzs
The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted.
According to the error, selectedValue is an object so you can not use the async pipe.

Angular Reactive Forms: Dynamic Select dropdown value not binding

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)

How to use reactive forms inside ng-template

I have just started with Angular 4 and I need to develop a CRUD grid, where the user can add, edit or delete rows.
During my research I found this article where it shows how to create the grid and also the actions: Angular 4 Grid with CRUD operations.
Looking at his code, what called my attention was the way he is using the ng-template to toggle between edit/view mode.
<tr *ngFor="let emp of EMPLOYEES;let i=idx">
<ng-template [ngTemplateOutlet]="loadTemplate(emp)" [ngOutletContext]="{ $implicit: emp, idx: i }"></ng-template>
</tr>
On the article he uses template driven forms to edit the row. However, I was trying to change to reactive forms.
In my attempt to do that, I tried to replace the [(ngModel)] to formControlName and I got some errors. My first attempt I tried to add the [formGroup] at the beginning of the template html inside form element. But when I tried to run and edit the row, I got the following error:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
When I tried to move the [formGroup] inside the ng-template it works, however I was not able to bind the value to the fields and I had to set the values in the loadTemplate function:
loadTemplate(emp: Employee) {
if (this.selemp && this.selemp.id === emp.id) {
this.rForm.setValue({
id: emp.id,
name: emp.name
});
return this.editTemplate;
} else {
return this.readOnlyTemplate;
}
}
This works and show the values inside the fields in a read only mode :(
Here is the Plunker of what I have got so far.
How can I make a reactive form work with ng-template and how to set values to edit the entries?
Any help is appreciated! Thanks
Actually your form is not readonly, you are just constantly overwriting the input you are entering. Since you are having a method call in template (which is usually not a good idea), loadTemplate gets called whenever changes happen, which in it's turn means that
this.rForm.setValue({
id: emp.id,
name: emp.name
});
gets called over and over whenever you try and type anything. We can overcome this with instead setting the form values when you click to edit. Here we also store the index so that we can use it to set the modified values in the correct place in array, utilizing the index could perhaps be done in a smarter way, but this is a quick solution to achieve what we want.
editEmployee(emp: Employee) {
this.index = this.EMPLOYEES.indexOf(emp)
this.selemp = emp;
this.rForm.setValue({
id: emp.id,
name: emp.name
});
}
so when we click save, we use that index...
saveEmp(formValues) {
this.EMPLOYEES[this.index] = formValues;
this.selemp = null;
this.rForm.setValue({
id: '',
name: ''
});
}
Your plunker: https://plnkr.co/edit/6QyPmqsbUd6gzi2RhgPp?p=preview
BUT notice...
I would suggest you perhaps rethink this idea, having the method loadTemplate in template, will cause this method to fire way too much. You can see in the plunker, where we console log fired! whenever it is fired, so it is a lot! Depending on the case, this can cause serious performance issues, so keep that in mind :)
PS. Made some other changes to code for adding a new employee to work properly (not relevant to question)

Using ui-select2 with ng-repeat does not set the model correctly

First, some background:
When using ui-select2, you have to supply an initSelection function in the select2 config object. If you don't, you'll get an error (but the functionality won't be affected. Everything will still work as expected).
To illustrate, see this plunkr. When you select an item from the dropdown menu, it'll work, but you'll get the following error:
Error: cannot call val() if initSelection() is not defined
Adding an empty function to initSelection fixes the error. You can uncomment it in the above plunkr to see that.
The problem:
When using ui-select2 in conjunction with ng-repeat, it just doesn't update the model.
Controller:
// for this demo, `users` is injected into the controller
$scope.users = users.slice(0, 2);
$scope.select2Config = {
placeholder: 'Select User...',
query: function ( options )
{
// `users` in this demo is injected into the controller.
// in the real world this would be an ajax request
options.callback({ results: users });
},
// Without initSelection, I get the above error.
// Regardless, the model isn't updated.
initSelection: angular.noop,
formatSelection: select2format,
formatResult: select2format,
};
function select2format ( user )
{
return user.first + ' ' + user.last;
}
View:
<ul>
<li ng-repeat="user in users">
<input type="text" ng-model="user" ui-select2="select2Config">
</li>
</ul>
When selecting an item from the dropdown list, the model isn't updated. If there's no initSelection in the config I get the above error, but adding it still doesn't update the model.
Here's a plunkr demonstrating the above.
The question:
How do I make ui-select2 update the model in an ng-repeat?
Try using inheritance:
http://plnkr.co/edit/jyJaYU4DQX1LROD6nQaw?p=preview
Also I you were calling "user in users" on the ng-repeat but then also setting the ng-model to "user". I am not sure why.
As to initSelection behavior, I do not have an answer.
edit
I updated the plunker. The ng-repeat directive creates a new scopes - true, but all of its children (or repeated items) are siblings. You had bound "user" (that was being defined/transcluded by ng-repeat) to the ng-model of each select (and expecting that the "users" array would be updated if you changed the model).
I believe ng-repeat is a one way binding top down
Therefor what I showed was correct but admittedly lazy. I bound both selects to the same model but I just as easily bound them to seperate properties on the "selected" object using $index.
Point is: if you want two way binding then write a new directive, but it is easier to do something similar to what I show.
PS: In practice I populate a "lookups" object that has some arrays for the select/pull-downs and use a seperate object called "user" that holds what the user has selected. This is pretty much pulled right out of the (older) documentation.

Categories

Resources