Array being treated as string by angular with loopback backend - javascript

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

Related

Result is not display in autocomplete (Angular)

I use ng-prime <p-autocomplete> for display values via search in the back-end
here is my html
<p-autoComplete [(ngModel)]="agent" [suggestions]="filteredAgents" name="agents" (completeMethod)="filterAgents($event)" [size]="10"
placeholder="Agents" [minLength]="3"></p-autoComplete>
At the component.ts I initialize array like this at start of the component
filteredAgents: string[] = [];
and I have a method to send query to back end and push it to array
filterAgents(event) {
let query = event.query;
this._agentsService.getAgentSearch(query).subscribe(result => {
result.items.forEach((value) => {
this.filteredAgents.push(value.name);
console.log(this.filteredAgents);
});
});
}
I see filtered value in console, but I don't see it in suggestions.
Where can be my problem?
AutoComplete either uses setter based checking or ngDoCheck to realize if the suggestions has changed to update the UI. This is configured using the immutable property, when enabled (default) setter based detection is utilized so your changes such as adding or removing a record should always create a new array reference instead of manipulating an existing array as Angular does not trigger setters if the reference does not change. ( Angular documentation )
Array.prototype.push doesnt create a new reference it rather mutates the original array. So you need to make a new one.
filterAgents(event) {
let query = event.query;
this._agentsService.getAgentSearch(query).subscribe(result => {
this.filteredAgents = [...result.items.map(e => e.name)]
});
}
I maped the result to extract the names.
If filtered agents is an object array try adding field="name" to the directive attributes.
Here name is a field in the object. The directive uses this field to display in suggestions

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)

Knockout Observable Array of Observable Array

I have passed a model to my View Model from .Net, which is a Object, containing a few fields, and a List, and each of those has another List.
So, an object with a List of Lists.
On my View, I represent this as some data, with a list of Tabs (The first list), and then each tab has a grid of data.
In my Knockout View model, I just have:
self.Name = ko.observable("");
self.Width = ko.observable(0);
self.Height = ko.observable(0);
self.TemplateGroups = ko.observableArray();
So, my main object, with an observable array (the first list). But, the list within that, isn't observable.
So when rendering my tabls, I do:
<div class="tab-content" data-bind="foreach: TemplateGroups">
And that renders each tab. And then within each tab, I do this:
<tbody data-bind="foreach: $data.Components">
<tr style="cursor: pointer" data-bind="click: $parents[1].ClickedIt">
('Components' is the name of the list within the outer list)
Problem is, on my ClickIt function, I am displaying the Idof the row I clicked, and it works. And then I am just trying to change the 'Description' field of this outer list, but ... it's not a function, so, no observed:
self.ClickedIt = function () {
alert(this.Id);
this.Description("Craig");
}
The alert shows the Id, but error on Description("Craig"), as it's not a function.
How can I make the list, within my ObservableArray, observable?
(To be clearer, my model I pass in is an object, which contains a List called TemplateGroups... and then each item in that list, contains a List called 'Components'. It's the Id of one of those Components that I am displaying, but I need to make that list Observable.
Edit: This seems to be what I am looking for (http://jsfiddle.net/rniemeyer/bZhCk/), but ... I am not defining my array the same way. Instead, they're being passed in from a .Net class (So, converted to JSON, I think). And that .Net class contains a List of another .Net class, which has a List).
Note, My load method:
self.loadData = function () {
$.get("/api/PlateTemplate/Get", { id: self.TemplateId() }).done(function (data) {
self.Name(data.Description);
self.Width(data.Width);
self.Height(data.Height);
self.TemplateGroups(data.PlateTemplateGroups);
});
}
The content of self.TemplateGroups is data.PlateTemplateGroups, that is an array and its content are not observable properties (they are javascript objects).
If you want that this objects in this array become observables, you could use the Mapping Plugin:
self.loadData = function () {
$.get("/api/PlateTemplate/Get", { id: self.TemplateId() }).done(function (data) {
self.Name(data.Description);
self.Width(data.Width);
self.Height(data.Height);
self.TemplateGroups(
ko.mapping.fromJS( // <--- new
data.PlateTemplateGrou‌​ps));
});
}
This way, all properties become observables.
If you need to convert this data to a Json format you could use ko.mapping.toJS():
ko.mapping.toJS(self.TemplateGroups)

Avoiding another API call with Angular and JSON

Probably an easy question, but I am making a call to an API that returns a full list of products in JSON. The products are listed under 4 categories in the JSON - 'connectivity','cables','routers' and 'servers'. Using the getProducts() function below, I assign the list of 'connectivity' products to a variable called $scope.connectivitylistOfProducts - and this is what I display to the user in the UI as a default.
productsServices.getProducts()
.then(function (allProducts) {
$scope.listOfProducts = allProducts.data.category[0];
$scope.connectivitylistOfProducts = allProducts.data.category[0].connectivity;
})
.finally(function () {
});
In the UI, I have a select box that's contains a list of the categories where the user can change to view the products under the category they choose. changeProduct() is what is called to change the category
$scope.changeProduct = function () {
// change the product
};
I am already loading the full list of categories and products into $scope.listOfProducts and I dont want to make another API call by calling getProducts again. I'm not sure how to set up another variable (for example $scope.routerslistOfProducts) and assing the correct products to it. Could anyone tell me the best way to handle this? Many thanks
Could you try to access lists by array notation:
you have:
$scope.listOfProducts = allProducts.data.category[0];
you could create a category variable:
$scope.category = 'connectivity';
and to access using, for example:
<div ng-repeat="product in listOfProducts[category]">
If your payload has all the arrays then on API call back assign to scope variable say $scope.mylist, now you bind $scope.mylist.categories to drop down and on drop down change send category Id to change function () and filter using for loop and equal operator , while running through loop I.e filtering , which ever product matches category push to a variable say $scope.filteredproducts ....and here you got your filtered products.
This is simple way to understand , there are better ways to maintain filtered arrays too.

Angular text input filter initializes with no results

I have a basic angular app that sorts and filters a list of species from a local json file. When the app initializes none of the species are returned. When you begin to type some species are returned, and if you delete your search terms all species are returned.
I would like all species to be returned when the app initializes.
http://plnkr.co/edit/0DOwSvtaepSfFWKR8UGS?p=info
I think it's neater to use the inline notation for filter, which avoids this problem:
<div ng-repeat="animal in species | filter:query | orderBy:orderProp")>
NOTE: you can then remove the code that sets up the $watch on query
EDIT: as you specifically want it in the controller:
The reason your filter is initialising to blank, is because your $scope.species data is being populated asynchronously. When the first $watch is triggered, there is no data to filter. This stays the case until you input a query.
To solve this, set up the $watch after the data has arrived, like so:
$http.get('species.json').success(function(data) {
$scope.species = data;
$scope.$watch('query', function (query) {
$scope.filteredData = $filter('filter')($scope.species, query);
});
});
Alternatively you could manually run the filter function once, inside the success callback.
Have you tried setting filteredData to the data returned from the get?
Example

Categories

Resources