Angular 2 Forms: binding html select to collection of objects - html

I would like to display a collection of selectable objects in an angular 2 application (RC 5, forms 0.3.0)
<select [(ngModel)]="selectedItem">
<option *ngFor="let item of selectableItems"
[value]="item">
{{ item }}
</option>
</select>
<div> {{ selectedItem }} </div>
The list itself is correctly displayed.
But all that is displayed for 'selectedItem' is [object Object]. When accessing the item in code, I get the corresponding string "[object Object]". I tried switching to ngValue but that produces the same results.
The whole thing works when I use primitive values instead of objects. But I suspect that I am missing some crucial point here.
Thanks for your help. I've been wasting several hours on Internet search and trial and error. Maybe somebody has encountered the same problem.
Edit (24-08-16): I came across this tutorial which explains how to create common forms widgets today. Maybe it's useful for somebody who lands on this page: https://scotch.io/tutorials/how-to-deal-with-different-form-controls-in-angular-2

From your code and explanation, I assume "item" is a complex object, and not a simple object. So to display it in HTML you would use:
<div> {{ selectedItem | json }} </div>
In code you have to access the "item" properties (as in item.value or whatever the item's properties are. If you want to work with simple values you can do something like this:
<select [(ngModel)]="selectedItem">
<option *ngFor="let item of selectableItems"
[value]="item.id">
{{ item }}
</option>
</select>
This is of course assuming item is a complex type.
Edit
After some digging (because this has plagued me as well), I found the solution I really wanted! To use complex object you just need to use [ngValue] when binding the value! The fix is described at https://github.com/angular/angular/issues/4843. So the above will look like this:
<select [(ngModel)]="selectedItem">
<option *ngFor="let item of selectableItems"
[ngValue]="item">
{{item}}
</option>
</select>
And then selectedItem will the the correct item from the list, including all it's properties.

Related

saving part of JSON in a function invoked in the html file

I'm developing in Angular.
I'm trying to save part of a JSON in a variable inside a function called in an HTML file:
<select id="postazione" onchange="postazioneSelezionata()">
<option value="" selected disabled >Scegli una postazione</option>
<option *ngFor="let postazione of postazioniNome" value="{{postazione}}">{{postazione}}</option>
</select>
(the onchange="postazioneSelezionata()").
it seems works, because if I try to print the value I'm interested in inside the console.log, it is shown correctly.
postazioneSelezionata(){
this.postazione = document.getElementById("postazione").value;
console.log(this.postazione);
}
inside the function i'm also trying to show this in the terminal:
console.log(this.fileJson.ricette[this.postazione]);
Here comes the problem...
If i try to show the part of the JSON this.fileJson inside the console.log(), it return me an error:
ERROR TypeError: Cannot read property 'ricette' of undefined
at HTMLSelectElement.postazioneSelezionata [as __zone_symbol__ON_PROPERTYchange] .
i've tried to show it in another function (not called from an html onchange event)
this.postazione = "GRIGLIA";
console.log(this.fileJson.ricette[this.postazione])
and it works... this code (called in another function) show me the portion of the fileJson JSON i'm trying to obtain
Possibly this post will be of help.
Additionally, even though you say your onchange works, the standard syntax for such events with Angular, as m.akbari mentioned in his comment, is (change):
<select id="postazione" (change)="postazioneSelezionata()">
...
</select>
And from there, unless you're not particularly bothered about the value of the change and just care to know that something was changed, you would probably pass the value in that changed...
<select id="postazione" onchange="postazioneSelezionata($event.target.value)">
...
</select>
Similar changes, syntax-wise, in your options for entering your value:
<option *ngFor="let postazione of postazioniNome" [value]="postazione">{{postazione}}</option>
Input/output binding, two-way binding, event binding... all are fairly integral to Angular, it's worth getting them figured out.
Again, see the post linked at the top for a more rounded answer and examples to using selects with Angular.

ngOptions not working, even with base example

I am very new to Angular and a bit overwhelmed.
As far as I understand, I have a component from where I can display data like {{ this }}, which works fine. Now, I want to have a select of that data which is in the component.ts. It is an array of strings:
private strategies: string[] = ["ASAP", "W&T"];
Now, in my component.html, I want to create a select with these strings as options.
<select ngModel="result" ngOptions="value for value in strategies">
</select>
{{ result }}
But the options list is always empty. {{ strategies }} yields ASAP,W&T, which is correct. Even if I use a base example such as:
<select ngModel="result" ngOptions="value for value in ['a', 'b']">
</select>
the options list is completely empty. What am I missing?
You need to use ngFor on options to build the options in angular (v2+):
Also make sure strategies defined in component are public and hence accesible in template.
<select [(ngModel)]="result">
<option *ngFor="let strategy of strategies" [value]="strategy">
{{strategy.name}}
</option>
</select>
Also make sure you use variable name from *ngFor for binding the value and display attribute for options.
ng-options was part of angularJS and is not used in Angular v2+.
AngularJS we use ngOptions directive create the list of element for select but from Angular 2+ and above Angular provide the new structural directive *ngFor which will help to create dynamic list.
And to get selected value from the html Angular provide two way data binding directive called ngModel. To bind two data in Angular 2+ is little bit different like below
Example -
<select [(ngModel)]="result">
<option *ngFor="let strategy of strategies" [value]="strategy">
{{strategy.name}}
</option>
</select>
Hope this help!

Using 'this' keyword in Angular component's template

Let's say we have a prop variable in the component class and we use it via interpolation in the template (stackblitz demo):
component class:
#Component({...})
export class AppComponent {
prop = 'Test';
...
}
template:
<p>{{ this.prop }}</p>
<p>{{ prop }}</p>
Why in Angular it's possible to use this keyword in templates without any warnings/error (even in AOT mode)? What's behind it?
Edit
According to the remark in the answer: this refers to the component itself for which the template was rendered. But I can also create a template variable and access to it using this:
<input #inp> {{ this.inp.value }}
In this case we don't have an inp variable in the component class and I still get the access to it using {{this.inp...}}. Magic?
I don't think somebody can give a very much exact answer here (maybe somebody from Angular CLI team), however the outcome I came to is that the component renderer fully ignores this keyword in the places where it seems valid (with some exceptions).
Proof
<input #heroInput value="0">
This prints the component JSON without heroInput: {{ this | json }}
<input #heroInput value="0">
This prints 0: {{ this.heroInput.value }}
<div *ngFor="let val of [1,2,3]">
<input #heroInput [value]="val">
Overrides heroInput with current value: {{ this.heroInput.value }}
</div>
This prints 0: {{ this.heroInput.value }}
One can assume from the above that this is similar to AngularJS (angular 1) scope, where the scope contains the component properties.
It does not explain why heroInput is not listed in this | json still.
However the following is totally broken:
{{ this['heroInput'].value }}
It gives an error: cannot get value of undefined. It should, not, it must work, unless (the only explanation) this is just ignored in every case but
{{ this | json }}
where it refers to the component, because this is the only way to debug the whole component object from the template. Maybe there are some other exceptions, still.
Updated stackblitz
this refers to the component itself for which the template was rendered. On the template you can access only members of the component. This means that this is implicitly added to each property which you use in the template.
This two accesses are the same - the 2nd one implicitly use this in front of it.
<p>{{ this.prop }}</p>
<p>{{ prop }}</p>
The same is when you use this in the component. When you want to access prop in the component you need to prefix it with this.prop to inform that you are accessing property of the component, not a local variable.
I felt we can't get a proper explanation on this but,
I went through a case where i will be creating the members of the component dynamically.
In here it might go wrong, if i don't use this.member (In my case it was actually this[member]).
Create a member in component like,
this.members(prop=> this[prop]={})
Usage in template will be like,
{{this[prop]}} will give expected result.
{{prop}} will not give expected result because
it will print value of list.

ng select not working with parent div ng-repeat

If I am using exact text for comparison in ng-init or ng-selected then it works fine for eg. ng-init="status='OPEN'" but this is not working when I am using loop variable data.status. Any suggestion?
<div ng-repeat="user in filtered = userList | filter:search">
<select ng-selected="{{user.status == item.id}}" ng-options="item.name for item in item track by item.id" ng-model="status" name="status">
</select>
</div>
$scope.item = [
{id: 'OPEN', name: 'OPEN'},
{id: 'RESCHEDULE', name: 'RESCHEDULE'},
{id: 'CANCEL', name: 'CANCEL'},
{id: 'CLOSED', name: 'CLOSED'}
];
It's a little unclear what exactly it is you are trying to do, and I think you may be trying to do a lot in one go when you should break it down more. One things that does stand out is that you are using ng-selected wrong.
Firstly you don't need to wrap the conditional in handlebars {{ }}. (That implies a 2 way binding to a JavaScript object, and makes no sense in this context).
Secondly, the ng-selected directive can't be applied on a select element, only on an option element.
To do what I think you are trying to do (pre-select an option) depends on how you are working. Here are a couple of ways
2 way binding
Remember that the data that you are binding to with ng-model is bound with a 2 way binding which means that the selection will match the underlying data. In this case you shouldn't need to do anything fancy in the template.
<div ng-repeat="user in userList">
<select ng-options="item.name for item in itemList track by item.id" ng-model="user.selectedItem">
</select>
</div>
Instead you just handle it in your controller:
//This shows a static example. Change to whatever is appropriate, probably in a for loop etc
$scope.userList[1].selectedItem = $scope.itemList[1]
ng-repeat inside the select
Alternatively you may want to take the approach of using ng-repeat inside your select element. This then allows you to specify the behaviour more closely on an option by option basis.
<div ng-repeat="user in userList">
<select ng-model="user.selectedItem">
<option ng-repeat="item in itemList" ng-selected="user.status === item.id">
</select>
</div>
(N.b. I have edited some of your variable names for clarity eg. changed 'item' to 'itemList')

Tooltip not working inside a tag with ngFor attribute

Could someone explain to me why the tooltip in this piece of code using Angular 4 templates doesn't work?
<template ngFor let-channel [ngForOf]="channels">
<td>
<em *ngFor="let color of findBallsColor()" class="fa fa-circle {{ color }}" [tooltip]="id"></em>
</td>
</template>
<ng-template #id>
test
</ng-template>
If I remove the *ngFor inside the <em> tag it works fine (showing just one element obviously). I'm quite new to Angular so probably I'm missing some understanding of how it really works here.
EDIT
I found out the problem comes from the type returned from the Typescript function. In the code above, the list returned by findBallsColor() is actually an object that contains 4 fields. When I change it to just return a string it works. So the code looks similar to this:
HTML:
<template ngFor let-channel [ngForOf]="channels">
<td>
<em *ngFor="let color of findBallsColor()" class="fa fa-circle {{ status.color }}" [tooltip]="id"></em>
</td>
</template>
<ng-template #id>
test
</ng-template>
TS:
findBallsColor():any[] {
return [{"statut":"ERROR","name":"name","otherField":"value","anotherField":"value"}];
}
Does anyone know the why of this behaviour?
I had a similar problem, here's an excerpt from this Github issue, that details what is wrong with the way you get your data for your *ngFor.
Calling a function from the template is not a good practice and this is an example of why this recommendation exists.
When the tooltip is fired in the first case, it starts an angular detection cycle, which in turn calls items() again and it tries to show the tooltip again and it starts another angular detection cycle and it goes on and on and on repeatedly.
At first, the recommendation exists to avoid performance issues, but it has other consequences like in your case.
If you put a console.log in items(), you'll see it keeps being called again and again...
If calling a function is mandatory, use a pipe instead.
So, in your code, if you used a pipe or an observable or array of some sort, then your tooltip would work, but currently it just keeps calling the function.
This worked for me - add data-toggle="tooltip" to the html tag - aka the tooltip.
Then target the tooltip via the body $('body').tooltip({selector: '[data-toggle="tooltip"]'});