Ng-Zorro select not showing selected items with NgModel - html

I am using Ng-Zorro's multiple select, which is in a drawer. When opening the drawer, I give the select element a list of options and a list of items that are already chosen. The list of options to pick from works fine, but the already selected items do not show. This can be seen here as well: StackBlitz
The code:
<nz-select [(ngModel)]="selectedAttributes"
[nzMaxTagCount]="3"
[nzMaxTagPlaceholder]="attributeTagPlaceHolder"
nzMode="multiple"
name="changeAttributes"
id ="changeAttributes"
nzPlaceHolder="Please select">
<nz-option *ngFor="let attribute of allAttributes" [nzLabel]="attribute.name" [nzValue]="attribute"></nz-option>
</nz-select>
<ng-template #attributeTagPlaceHolder let-selectedList> "and " {{ selectedList.length }} " more items" </ng-template>
where the allAttributes list is formatted like this:
allAttributes= [
{
"id": 1,
"name": "Mask"
},
{
"id": 2,
"name": "Intensive"
},
{
"id": 3,
"name": "Family"
},
{
"id": 4,
"name": "Isolation"
}
];
and where the selectedAttributes is one or more of the items in the allAttributes list:
selectedAttributes= [{"id": 1,"name": "Mask"}];
No matter how I create or format the selected attributes list (it can be straight from the allAttributes list), the placeholder cannot be seen and the select is empty, plus when picking all options, the nzMaxTagPlaceholder shows there is an extra item picked.
Can anyone show me the way to set the selected items dynamically?

Try sth like below.
selectedAttributes = [this.allAttributes[0]];
Since
{"id": 1,"name": "Hapnikumask"}
is a complex object its equality will be checked by references. So you are defining a new object as selected it will be different from the source object.

use compareFn in your nz-select like this.
<nz-select
[(ngModel)]="selectedValue"
[compareWith]="compareFn"
(ngModelChange)="log($event)"
nzAllowClear
nzPlaceHolder="Choose"
>
in typescript file:-
compareFn = (o1: any, o2: any): boolean => (o1 && o2 ? o1.id === o2.id : o1 === o2);

Related

Vue - error in render when trying to display data from nested object

I'm dealing with a strange problem in Vue. I'm trying to display a list of employees in the table. Employees are provided by a REST service and response looks exactly like this (shown below is one item from the paginatedList):
{
"id": 10a,
"code": 0000,
"firstName": "John",
"lastName": "Doe",
"level": "WORKER",
"specialization": {
"id": 20,
"name": "Default",
"taem": {
"id": 2,
"name": "Builders",
"system": false,
"teamLead": null,
"specializationDtos": null,
"categoryDtos": null
}
},
"team": {
"id": 2,
"name": "Builders",
"system": false,
"teamLead": null,
"specializationDtos": null,
"categoryDtos": null
},
"coachDto": null,
"roles": [
"DL"
]
}
In Vue, I'm using Vuex actions to preprocess raw data and return paginated list of employees. Then I'm trying to print it in the table using v-for like this:
<tr v-for="item in paginatedList" :key="item.id"
v-on:click="selectRow(item)"
v-bind:class="{selectedRow: selectedItem === item}"
>
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.team.name}}</td>
</tr>
And this is where it fails (kind of). Even though the table is displayed with no employee missing the team name, I'm getting warning from Vue:
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
After clicking on the line of code, which causes the warning, it points to the place where I'm trying to render {{item.team.name}}
My first thought was that the employee with missing team was simply not displayed. In order to check it I came up with following workaround:
<td>{{getTeamName(item.team)}}</td>
And the method:
getTeamName(team) {
return (team ? team.name : 'FAILED');
}
Warning/error disappears but there is no employee with FAILED team. What exactly is the problem with this? Is it possible to display properties of nested object like that or should I avoid it?
This can happen when the object your iterating doesn't yet have the data you're expecting but by the time you come to debug it, it does. I find this most often when using data which you're getting from a promise. With that in mind, just add a check to make sure that your data has loaded properly, in your case, this should suffice:
<tr v-for="item in paginatedList" :key="item.id"
v-on:click="selectRow(item)"
v-bind:class="{selectedRow: selectedItem === item}"
>
<div v-if="item.team">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.team.name}}</td>
</div>
</tr>
I generally have a dataLoaded prop on my component which is initialised as false but set to true once I've finished messing with my data to avoid issues like this. I'd then check v-if="dataLoaded" around my whole component.
It looks as though you have mis-spelled team in your object:
"taem": {
"id": 2,
"name": "Builders",
"system": false,
"teamLead": null,
"specializationDtos": null,
"categoryDtos": null
}
which I guess is why it can't render name of undefined.

Angular 2 - Interpolation for NGX-Charts Results within ngFor

I'm trying to show charts based on some json. I made a pipe to access the various parts of the json object but don't know how to use the data, which looks like this:
[ { "name": "Completed", "value": 50 }, { "name": "Remaining", "value": 50 } ]
within the ngFor.
So normally I would do something like this:
<ngx-charts-advanced-pie-chart
mdTooltip="Click to change unit"
[tooltipDisabled] = "true"
[scheme]="colorScheme"
[results]="results" <----this is where the results normally go
[gradient]="gradient"
[view]="view"
(select)="onChartClick()"
>
</ngx-charts-advanced-pie-chart>
And I want to try something like this:
<div *ngFor="let chair of targetChairs | chairDimensions">
<ngx-charts-advanced-pie-chart
mdTooltip="Click to change unit"
[tooltipDisabled] = "true"
[scheme]="colorScheme"
[results]="{{ chair.value.data.height | json }}"
[gradient]="gradient"
[view]="view"
(select)="onChartClick()"
>
</ngx-charts-advanced-pie-chart>
</div>
This doesn't work but is there a way to achieve this?
So I found a similar issue on a git page here which solved my problem, so here is what I used in case someone else has this problem:
<div *ngFor="let chair of targetChairs | chairDimensions">
<ngx-charts-advanced-pie-chart
mdTooltip="Click to change unit"
[tooltipDisabled] = "true"
[scheme]="colorScheme"
[results]="chair.value.data.feet"
[gradient]="gradient"
[view]="view"
(select)="onChartClick()"
>
</ngx-charts-advanced-pie-chart>
</div>

Json to excel using power query

I have some json on a website that i want to convert to excel using the power query option from web. But I ran into a small problem. My json looks like this:
[
{
"id" : 1,
"visitors" : 26,
"some_number" : 1,
"value" : 3500
},
{
"id" : 2,
"visitors" : 21,
"some_number" : 5,
"value" : 2000
}
]
but when i use from web i get this:
I can drill down into a record,convert it to a table, transpose and use first row as header but then i get just one row. How can i get all of my data to the table and not just one row?
First I would use the List Tools / Transform menu (it should be automatically selected) and click the To Table button. This will give you a single-column table with 2 rows. Then I would click the small Expand button - it will appear in the column headings, just to the right of "Column1". Uncheck the Use original column name ... option and you will get a table of 4 columns and 2 rows.
Here's the full script I generated:
let
Source = Json.Document(File.Contents("C:\Users\Mike.Honey\Downloads\json2.json")),
#"Converted to Table" = Table.FromList(Source, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
#"Expanded Column2" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"id", "visitors", "some_number", "value"}, {"id", "visitors", "some_number", "value"})
in
#"Expanded Column2"
The Table.FromRecords() function is suitable for that sample data:
let
Source = Json.Document("[{""id"": 1, ""visitors"": 26, ""some_number"": 1, ""value"": 3500}, {""id"": 2, ""visitors"": 21, ""some_number"": 5, ""value"": 2000}]"),
AsTable = Table.FromRecords(Source)
in
AsTable
I have Excel Professional 2016 and I don't see the JSON option, but I can get to it via the "Other Sources" query option. Here are all the steps to convert a JSON array to a table.
Data > New Query > From Other Sources > From Web
Enter URL : "file:///C:/temp/document.json", (or a http web url) Press OK
A row is displayed with a "items" property and List type,
Click on "List", the items in list are displayed
Press "To Table" button in upper left corner, Press OK in next
dialog
A table with one column named "Column1" is displayed
Press the button next to the column name (has left and right arrows
on it)
Properties in the row object are selected
Uncheck "User Original column name as prefix", Press "OK"
Press "Close & Load" button in upper left corner
my file looks like
{
"items": [{
"code": "1",
"name": "first"
}, {
"code": "2",
"name": "second"
}, {
"code": "3",
"name": "third"
},
]
}
You need to convert the list to a table first, then you can expand the record column and proceed from there. If no luck, then you can take a look at this video I created recently for a similar question.

Prevent change of 'copied' object affecting original using ng-repeat in angular

I have a setup in angular which displays a json string called 'items'. Each item contains an array of field ids. By matching the field ids, it pulls information for the fields using a seperate 'fields' json string.
{
"data": [
{
"id": "1",
"title": "Item 1",
"fields": [
1,
1
]
},
{
"id": "2",
"title": "Item 2",
"fields": [
1,
3
]
},
{
"id": 3,
"title": "fsdfs"
}
]
}
You can copy or delete either the items or fields, which will modify the 'items' json.
Everything works except when I copy one item (and its fields), and then choose to delete a field for that specific item.
What happens is that it deletes the field for both the copied item AND the original.
Plunker -
http://plnkr.co/edit/hN8tQiBMBhQ1jwmPiZp3?p=preview
I've read that using 'track by' helps to index each item as unique and prevent duplicate keys, but this seems to be having no effect.
Any help appreciated, thanks
Edit -
Credit to Eric McCormick for this one, using angular.copy with array.push solved this issue.
Instead of -
$scope.copyItem = function (index) {
items.data.push({
id: $scope.items.data.length + 1,
title: items.data[index].title,
fields: items.data[index].fields
});
}
This worked -
$scope.copyItem = function (index) {
items.data.push(angular.copy(items.data[index]));
}
I recommend using angular.copy, which is a "deep copy" of the source object. This is a unique object from the source one.
It may seem slightly counter-intuitive, but a direct reference (as you're observing) interacts with the original object. If you inspect the element's scope after it's instantiated in the DOM, you can see there's a $id property assigned to the object in memory. Basically, by using angular.copy(source, destination), you ensure a copying of all the properties/values and having a unique object.
Example:
//inside the controller, a function to instantiate new copy of selected object
this.selectItem = function(item){
var copyOfItem = angular.copy(item);
//new item with same properties and values but unique object!
}
Egghead.io has a video on angular.copy.

Submit and edit value in the same form in AngularJS

I have just started using AngularJS.
I would like to send and edit value in the same form.
Here is my approach :
I fill the input with the scope array with ng-init.
And with the scope fields, I store the new value when I click on the button submit.
But it doesn't seem to work... I don't understand why.
index.html
<form class="form" name="myForm" novalidate>
<div class="col span12 input"><input type="text" ng-model="fields.name" ng-init="array.name" required /></div>
<div class="col span12 input"><input type="text" ng-model="fields.age" ng-init="array.age" required /></div>
<select ng-model="fields.po_id" ng-init="fields.po_id" ng-options="productOwner in productOwners" required></select>
ng
Add
</form>
controllers.js
$scope.productOwners =
[
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4
},
];
$scope.array =
[
{
"name": "Hello"
},
{
"age": "18"
},
{
"po_id": "2"
}
];
$scope.submit = function(fields){
}
I've created a working Plunker solving your issue.
You had two issues in your code:
1) ng-init="array.age" should be ng-init="fields.age=array.age" and same thing for the other ng-init.
You wasn't saving the value in fields so it wasn't updated.
2) Your array definition was faulty.
You should either define array as:
$scope.array = {
"name": "Hello",
"age": "18"
};
Or change the call to array in the HTML template like that:
array.name -> array[0].name
array.age -> array[1].age
EDIT:
Updated Plunker
The correct use of ng-options is
for array data sources:
label for value in array
select as label for value in array
label group by group for value in array
select as label group by group for value in array
for object data sources:
label for (key , value) in object
select as label for (key , value) in object
label group by group for (key, value) in object
select as label group by group for (key, value) in object
So I've changed your ng-options from ng-options="productOwner in productOwners" to ng-options="productOwner.id for productOwner in productOwners" which is of the type : label for value in array where the label is productOwner.id and the value is the object productOwner.
Moreover, the ng-init is not needed since fields.po_id has no value at the initialization.
But if you want to initialize the value, you can do something like :
ng-init ="fields.po_id = productOwners[0]"