Select option with ngModel - javascript

I have a project that has a select of several questions and I would like the first one to appear by default.
I have seen an example of how to achieve this using the index, of the type:
<select>
<option *ngFor="let answer of answers; let i = index" [value]="answer.id" [selected]="i == 2">
{{answer.name}}
</option>
</select>
and it works, but when I want to bind the select to a property of the component, it is no longer selected:
<select [(ngModel)]=searchterms.answerId>
<option *ngFor="let answer of answers; let i = index" [value]="answer.id" [selected]="i == 2">
{‌{answer.name}}
</option>
</select>
You can see an example here:
https://stackblitz.com/edit/angular-rv9vqi
As say in some answers the solution is to set a default value to the serachterm, but the problem I have, (I am not able to reproduce it in the playground) is that I receive those answers from a service that asks to a back, and when the component is built it still does not have them and it gives me an error .... how can I make it assign those searchterms the value once they exist in the service?

You can use Angulars two-way data binding to bind to the value attribute of the <select> element. In your example it would look like this:
<select [(value)]="searchterms.answerId">
<option *ngFor="let answer of answers" [value]="answer.id">{{answer.name}}</option>
</select>
Notice how binding to the value attribute cleans up the option element, by enabling you to remove the [selected]="i == 2" and let i = index
However, like the others have mentioned you will want to initialize the desired default value in your component code.
Here is the working StackBlitz Demo with your code

You need to set the searchterms.answerId with a proper default value. In your case
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular 6';
answers = [
{name: "Answer 1", id: '1'},
{name: "Answer 2", id: '2'},
{name: "Answer 3", id: '3'},
{name: "Answer 4", id: '4'},
];
searchterms = {
answerId: this.answers[1].id //Set the default value properly
};
}
stackblitz

Then you should bind searchterms in your component.
you can do it inside constructor as follows.
this.searchterms.answerId = this.answers[1].id;
Demo

Related

Angular 15, child component doesn't emit string to parent

I have this child component (filters.component.ts) from which I am trying to emit a string to the parent component. I had already done this once with a different component but it seems like Angular doesn't like me implementing an *ngFor to loop through a string array and pass the category string to the method? I've tried adding a console log to the onShowCategory() method in home.component.ts and it does not log any string values to the console, leading me to believe that the values are not being passed to the parent when the click event is activated. Here is the code (I've added arrows to point to the relevant lines of code, they are not part of my code and not the issue.):
filters.component.html:
<mat-expansion-panel *ngIf="categories">
<mat-expansion-panel-header>
<mat-panel-title>CATEGORIES</mat-panel-title>
</mat-expansion-panel-header>
<mat-selection-list [multiple]="false">
<mat-list-option *ngFor="let category of categories" [value]="category"> <--------
<button (click)="onShowCategory(category)">{{ category }}</button> <--------
</mat-list-option>
</mat-selection-list>
</mat-expansion-panel>
filters.component.ts:
#Component({
selector: 'app-filters',
templateUrl: './filters.component.html',
styleUrls: []
})
export class FiltersComponent {
#Output() showCategory = new EventEmitter<string>() <-------
categories: string[] = ['shoes', 'sports']; <-------
onShowCategory(category: string): void { <-------
this.showCategory.emit(category); <-------
}
}
home.component.html:
<mat-drawer-container [autosize]="true" class="min-h-full max-w-7xl mx-auto border-x">
<mat-drawer mode="side" class="p-6" opened>
<app-filters (showCategory)="onShowCategory($event)"></app-filters> <-------
</mat-drawer>
<mat-drawer-content class="p-6">
<app-products-header (columnsCountChange)="onColumnsCountChange($event)"></app-products-header>
{{ category }} <----- should display the category when selected
</mat-drawer-content>
</mat-drawer-container>
home.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: []
})
export class HomeComponent {
cols = 3;
category: string | undefined; <-------
onColumnsCountChange(colsNumber: number): void {
this.cols = colsNumber
}
onShowCategory(newCategory: string): void { <-------
this.category = newCategory; <-------
}
}
I have read through and followed the variables many times and I don't see the issue. From the child component template I pass the category to the onShowCategory method and emit it to the parent. From the parent I call the EventEmitter and pass the $event variable which should change the value of the category property in the home component. I've checked spelling, and tried moving the tags around. I don't see a console.log in the console when I add one to the method, and I cannot get the string to appear on the home template. What am I doing wrong?
your parent html should have messageEvent. Give this a try and see if it works.
<app-filters (messageEvent)="onShowCategory($event)"></app-filters>
<app-products-header (messageEvent)="onColumnsCountChange($event)"></app-products-header>
I also like to put console.log statements at every point when setting up EventEmitters, just to see where it's getting stuck.
After taking a break and spending a few more hours combing the internet for answers (to no avail) I decided to try something completely unintuitive and solved it.
I changed:
<mat-list-option *ngFor="let category of categories" [value]="category">
<button type="button" (click)="onShowCategory(category)">{{ category }}</button>
</mat-list-option>
To
<mat-list-option *ngFor="let category of categories" (click)="onShowCategory(category)" [value]="category">
<div>{{ category }}</div>
</mat-list-option>
and now it works, not entirely sure why the click event needs to be on the parent tag and cannot be on the button itself. The thought process that got me to this solution was that the click event seemed to not be activating and/or the strings in the categories array weren't being passed to the method properly. I hope this helps someone in the future.

p-dropdown is not displaying the labels correctly

I have trouble getting the dropdown component to work. The dropdown seems to detect the items it should display because it widens the itemlist according to the number of items in the array. However the spaces are all blank.
This is the same dropdown box as from the example at https://www.primefaces.org/primeng/#/dropdown (the first one with header 'simple')
However with me it doesn't display anything. I copy pasted the exact same code, the only difference are the imports. When i go to the github repository i can see that they import
import {SelectItem} from '../../../components/common/api';
and
import {DropdownModule} from '../../../components/dropdown/dropdown';
Where I use
import {SelectItem} from 'primeng/api';
and
import {DropdownModule} from 'primeng/dropdown';
When i try to use the imports from github then it says it can find dropdownmodule and selectitem at those locations.
Heres my code:
interface City {
name: string,
code: string
}
export class Test implements OnInit {
cities1: City[];
selectedCity: City;
constructor() {
this.cities1 = [
{label:'Select City', value:null},
{label:'New York', value:{id:1, name: 'New York', code: 'NY'}},
{label:'Rome', value:{id:2, name: 'Rome', code: 'RM'}},
{label:'London', value:{id:3, name: 'London', code: 'LDN'}},
{label:'Istanbul', value:{id:4, name: 'Istanbul', code: 'IST'}},
{label:'Paris', value:{id:5, name: 'Paris', code: 'PRS'}}
];
}
}
heres the html
<p-dropdown [options]="cities1" [(ngModel)]="selectedCity" placeholder="Select a City" optionLabel="name" [showClear]="true"></p-dropdown>
<p>Selected City: {{selectedCity ? selectedCity.name : 'none'}}</p>
Anyone know how i can fix this?
Thank you
remove optionLabel and code will work -
<p-dropdown [options]="cities1" [(ngModel)]="selectedCity" placeholder="Select a City" [showClear]="true"></p-dropdown>
OptionLabel : Name of the label field of an option when an arbitrary objects instead of SelectItems are used as options.
add optionLabel with the key name from the json array. The key you want to represent as label.
<p-dropdown optionLabel="label" [options]="cities1" [(ngModel)]="selectedCity" placeholder="Select a City" [showClear]="true"></p-dropdown>
Try this:
<p-dropdown
[options]="cities1"
[(ngModel)]="selectedCity"
placeholder="Select a City"
optionLabel="value.name"
[showClear]="true">
</p-dropdown>
Note this: optionLabel="value.name"

Formbuilder setvalue() is not working as expected when used on dropdown selects

Angular 2,4 formbuilder setvalue() is not working as expected when used on dropdown selects.
I have the following dropdown select that gets populated with Github organizations:
<select name="org" formControlName="organizations">
<option *ngFor="let org of organizations" [ngValue]="org">{{org.organization.login}}</option>
</select>
Here is the javascript code that sets the Github organization that should be selected.
this.pipelineForm = fb.group({
name:'',
organizations:'',
repos: '',
branches:'',
runtime:'',
versions:''
});
userService.getOrganizations().subscribe((org:github)=>{
let organizationName = org.data[0];
this.organizations = org.data;
this.projects.subscribe((p)=> {
p[1].project.subscribe((f)=>{
this.pipelineForm.get("organizations").setValue(f.organizations, {onlySelf: true});
//this.pipelineForm.patchValue(f);
});
});
});
I expect the corresponding dropdown option to be selected when I pass the value to the setValue(). Instead, I get a blank option. I also tried with patchValue(). No luck.
I struggled with the same problem and found Igor's answer. It was too vague so I dug around more. I finally figured it out, and Igor's answer is correct albeit lacking in detail. Hopefully this will help anyone else in the future trying to get setValue() on dropdowns and object models to work happily together.
Generally, the object model passed into setValue() should be the same as the objects you inject into your dropdown selector. E.g. if of type Organization, then the setValue should be also be of type Organization. It's not necessary, however, to share completely identical properties.
To get the setValue() or patchValue() to work with an object model (as opposed to some primitive type), use the [compareWith] function as Igor pointed out.
From node_modules/#angular/material/select/typings/select.d.ts:
compareWith is a function to compare the option values with the selected values.
The first argument is a value from an option. The second is a value
from the selection. A boolean should be returned.
In your html template,
<select [compareWith]="compareOrgs" name="org" formControlName="organizations">
<option *ngFor="let org of organizations" [ngValue]="org">{{org.organization.login}}</option>
</select>
And in your component.ts file, define the compareOrgs() function:
compareOrgs(c1: any, c2: any): boolean {
return c1 && c2 ? c1.orgId === c2.orgId : c1 === c2;
}
Don't forget to call the setValue() function, for e.g. in ngOnInit() or in the callback function if it's async and you're fetching data.
E.g. in the callback from OP's code above,
// "organization" is whatever object you're trying to patch on the dropdown
this.pipelineForm.get("organizations").setValue(organization)
So what all this does is, the compareOrgs function will compare the value of the selected value (which is the object you passed into setValue, now labelled as c2 in compareOrgs) with each of the option values (c1). Specifically, it compares the orgId properties of the selected and option values. This is how the FormControl knows which value to pre-select on the dropdown.
You can now access the object with [ngValue].
This is a good resource that helped me: https://codeburst.io/angular-material-select-module-the-comparewith-function-9dfdb4035373
A little example, we want to save a number. see [ngValue]="org.id" and this.myForm.controls['organization'].setValue(value);
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormControl, FormGroup } from '#angular/forms';
interface Iorganization {
id: number;
name: string;
}
#Component({
selector: 'app-select',
template: `
<form [formGroup]="myForm" novalidate>
<select name="org" formControlName="organization">
<option *ngFor="let org of organizations" [ngValue]="org.id">{{org.name}}</option>
</select>
</form>
<button (click)="setOrg(1)">Org 1</button>
<button (click)="setOrg(2)">Org 2</button>
{{myForm.value |json}}
`,
styleUrls: ['./select.component.css']
})
export class SelectComponent implements OnInit {
organizations: Iorganization[];
myForm: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.organizations = [
{ id: 1, name: "org 1" },
{ id: 2, name: "org 2" },
{ id: 3, name: "org 3" },
{ id: 4, name: "org 4" }
];
this.myForm = this.fb.group({
organization: 1
})
}
setOrg(value: number) {
this.myForm.controls['organization'].setValue(value);
}
}
if we want to save a object -see {{myForm.value |json}}- we need make a few changes. see that we put [ngValue]="org", but the setOrg() function use a object to make setValue. And not, it's not valid setValue({id:1,name:'org1'})
import { Component, OnInit } from '#angular/core';
....
template: `
<form [formGroup]="myForm" novalidate>
<select name="org" formControlName="organization">
<option *ngFor="let org of organizations" [ngValue]="org">{{org.name}}</option>
</select>
....`
export class SelectComponent implements OnInit {
...
ngOnInit() {
this.organizations = [
{ id: 1, name: "org 1" },
....
];
this.myForm = this.fb.group({
organization: null
})
}
setOrg(value: number) {
let organization=this.organizations.find(o=>o.id==value);
this.myForm.controls['organization'].setValue(organization);
}
Use the attribute compareWith on your select tag:
<select ... [compareWith]="compareByName">
Then implement a simple function in your component
compareByName(org1: Org, org2: Org): boolean {
return org1 && org2 ? org1.name === org2.name : org1 === org2;
}

Getting select option to show the initial selected option value using Angular

I am having a hard time trying to get the select input to show the initially selected option using Angular. I have tried many options but none have worked so far. Not sure why this is causing me such a headache. Any help would be great, thank you.
The select input shows nothing until I select an option. Would like to dynamically select what option I want selected when app loads.
You can view what I have setup so far with the link below:
https://stackblitz.com/edit/angular-wga92l?file=app%2Fhello.component.ts
Component:
#Component({
selector: 'master-input',
template: `
<form>
<div class="form-group">
<label for="Master-Products">Select Master Product</label>
<select name="pow.name" (ngModelChange)="change($event)" [ngModel]="selectedValue" class="form-control" id="Master-Products">
<option *ngFor="let pow of powers" [ngValue]="pow.id" [selected]="pow.id == 3">{{pow.name}}</option>
</select>
</div>
</form>
`,
styleUrls: ['./master-input.component.scss']
})
export class MasterInputComponent {
#Output() hasChanged: EventEmitter<number> = new EventEmitter();
powers: any[] = [{ id: 1, name: 'Bead Ruptor Elite' }, { id: 2, name: 'Bead Ruptor 12' }, { id: 3, name: 'Bead Ruptor 96' }];
selectedValue: string = 'Bead Ruptor 96';
change(value: number) {
this.hasChanged.emit(value);
}
}
just Add ngOnInit function
powers: any[] = [ 'power1', 'power2', 'power3' ];
ngOnInit(){
this.selectedValue ='power3';
}
change(value:string) {
this.hasChanged.emit(value)
}
It was tested in your fiddle.
Please see working image

angular2 - ngIf value is not updated in DOM

I'm currently facing an issue in updating a value in the DOM.
To explain it quickly and as simple as possible, I currently have two components (A and B). B inherits from A, uses A's view as template but also inject its template into A (ng-content) so B's view is like:
<A>
<template>
<div *ngFor="let item of items" (click)="selected($event, item)">
<span>{{item.name}}<span>
</div>
</template>
</A>
And A's view is like :
<ng-content select="template"></ng-content>
<div *ngIf="searching">Hey I'm looking for answer</div>
<div *ngIf="!searching">I already have my answer</div>
The component B has only one private variable which is the array of items and A's component has one specific method called by B which is :
private searching: boolean = true;
selected(event, value: any): void {
event.stopPropagation();
this.searching = false; // This.searching is updated in component but not in DOM
this.selectedItem = value; // BUT this.selectedItem is updated in component AND in DOM !!
}
Changing this.selectedItem works, but this.searching doesn't therefore only "Yes" is displayed since searching is always TRUE.
Here is the plunkr.
Can someone enlighten me please ? Thank you.
As per my comment, method in the parent class can be used in child class so it would work, unlike you coded.I don't know more about typescript extends and ng-content. you better need to refer extends ng-content functionality and scope of the <template-example> .
import {Component, NgModule} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
declare var searching: boolean
#Component({
selector: 'my-app',
template: `<ng-content select="template-example"></ng-content>`,
})
export class App {
private searching: boolean = true;
private selectedItem: any;
constructor(private zone:NgZone) {
}
selected(event, value: any) {
event.stopPropagation();
this.searching = !this.searching;
console.log(this.searching);
this.selectedItem = value;
}
}
#Component({
selector: 'app-inheritent',
template: `
<my-app>
<template-example>
<div *ngFor="let item of items" (click)="selected($event, item)">
<a>{{item.name}}</a>
</div>
<div *ngIf="!searching">
<span>Nope.</span>
</div>
<div *ngIf="searching">
<span>Yes</span>
</div>
</template-example>
</my-app>` ,
})
export class AppInheritent extends App {
private items: any = [
{name: "1"},
{name: "2"},
{name: "3"},
{name: "4"},
{name: "5"},
];
constructor() {
}
}
#NgModule({
imports: [ BrowserModule ],
declarations: [ App, AppInheritent ],
bootstrap: [ AppInheritent ],
schemas: [ NO_ERRORS_SCHEMA ]
})
export class AppModule {}
For those who could be interested... I finally found the answer thanks to #k11k2 and #Hareesh (I finally understood your answer) who enlightened me but this isn't entirely because of the extends. -- thanks.
Extends only confused my thoughts from the beginning.
In my example, I instantiated the component A (my-app) in my component B (app-inheritent). Because of the extends, I thought the component A and B would share the same variables and the same context but they absolutely don't.
In calling component B, we already have one context and in extending this latter with A, the two components share the same structure.
BUT, B is itself calling A so we got another context. A has the same structure than B but they aren't related in any way.
When I called the selected function, B actually called its PARENT selected method and not the function of the instantiated A component !
So, as #Hareesh said it, we have now multiple components. The only way to change the value is to make them communicate between each other.

Categories