I am using angular I want to send information about what element I selected in the select option. Specifically I want to send the data-value to a variable in my product-form.component.ts.
I tried using ngModel but I keep getting errors saying that it doesn't recognize (click)=selectCategory1(category) use a function I am using template forms for my forms that could be the reason. You can see my code live at :
https://stackblitz.com/github/RashellSmith/Dashboard-FrontEnd
Product Form component html
<div class="form-group">
<label for="productCategory">Product Category</label>
<select [(ngModel)]="model.category" (click)=selectCategory1(category) name="category" class="form-control" id="productCategory" (click)="value()">
<option *ngFor="let category of categories" (click)=selectCategory1(category) data-value="{{category.category}}" id={{category.categoryName}} >{{category.categoryName}}</option>
</select>
</div>
Product Form component ts
export class ProductFormComponent implements OnInit {
suppliers: Supplier[];
categories: Category[];
value: number;
model = new Newproduct("name",new Category( this.value,"name"),66,33,true,new Supplier(null,null),76);
selectCategory1(Category){
console.log(Category);
}
submitted = false;
get diagnostic() { return JSON.stringify(this.model); }
onSubmit() { this.submitted = true; }
constructor(private supplierService: SupplierService,private categoryService: CategoryService) { }
ngOnInit() {
this.supplierService.getAll().subscribe(data => {
this.suppliers = data;
});
this.categoryService.getAll().subscribe(data => {
this.categories = data;
});
}
}
You are definitely trying to over-complicate things. You need to bind a simple (change) method to your select list which would be be triggered on value change. You can either pass the value to this function using template reference variable as
<Select #select (change)="SelectChanged(select.value)">
Or you can bind [(ngModel)] directive and access that directly in your component class on (change)
A sample template code:
<select [(ngModel)]="selectedOption" (change)="GetSelectedValue(selectedOption)">
<option *ngFor="let option of options" [ngValue]="option">{{option}}</option>
</select>
And component class would look like
GetSelectedValue(val) {
//do something with val
}
Stackblitz at: https://stackblitz.com/edit/angular-r4d7ul
Related
I have a form where a user could add one/more div of Address on click of add button.
I want if user select options=5 from the dropdown, want to show and hide textbox in that particular address Div.
Component Code
get contactFormGroup() {
return this.form.get('Array') as FormArray;
}
ngOnInit() {
this.form= this.fb.group({
Array: this.fb.array([])
});
}
createContact(): FormGroup {
return this.fb.group({
ABC: [null, Validators.compose([Validators.required])],
Test: [null, Validators.compose([Validators.required])]
});
}
addContact() {
this.Group.push(this.createContact());
}
showValue(event) {
const selectedValue = event;
if (selectedValue === '5') {
this.showValuetxtbox = true;
} else {
this.showValuetxtbox = false;
}
}
As you are looping to add the divs, you could use a template reference variable on the drop down. e.g #select then refer to that in the *ngIf:
<form [formGroup]="addExpressionform">
<div formArrayName="expressionArray">
<div *ngFor="let item of contactFormGroup.controls; let i = index;" [formGroupName]="i">
<carbon-dropdown #select
(optionSelected)="showValue($event)"
[formControlN]="'UOM'"
[options]="listOptions" [formGroup]="item"
name="UOM"
>
</carbon-dropdown>
<carbon-text-input *ngIf="select.value == 5"
[formControlN]="'Value'"
[formGroup]="item"
name="Value"
>
</carbon-text-input>
<carbon-button type="primary" (click)="submit()" id="save-parameter">Save</carbon-button>
</div>
</div>
</form>
Simplified StackBlitz demo.
Take a look at this Stackblitz, it's referred to in the Angular docs and could serve as boilerplate to what you are trying to achieve.
You should isolate every possible type of question by creating a different class for each one, so you can shape the data and then use ngSwitch to dynamically create the HTML accordingly.
Question base class:
export class QuestionBase<T> {
controlType: string;
value: T;
key: string;
label: string;
// etc
constructor(options) {
// constructor logic
}
}
Some special class that inherents from base class
import { QuestionBase } from './question-base';
export class SpecialQuestion extends QuestionBase<string> {
controlType = 'specialQuestion';
type: string;
// special Question
specialValue: string;
constructor(options) {
super(options);
this.type = options['type'] || '';
}
}
Then, a question component:
<div [formGroup]="form">
<label>{{question.label}}</label>
<div [ngSwitch]="question.controlType">
// controls logic
<input *ngSwitchCase="'textbox'" >
<select *ngSwitchCase="'specialQuestion'"></select>
</div>
</div>
Then you throw this into a container component where you loop through the entire questions array.
This way your code will be future proof and reusable as you add/change functionality to your forms down the road. You won't have to create spaghetti to meet edge case requirements like an extra input field.
Right now able to communicate between two components. but don't know how to pass user (selected) entered value as Object via event emitter from MatDialog component to the parent component. Here I want to pass selected option value and text area value as object after clicking the submit button.
dialog.html
<mat-select [(ngModel)]="selectedIssue" [ngModelOptions]="{standalone: true}">
<mat-option [value]="option1">Option AA</mat-option>
<mat-option [value]="option2">Option BB</mat-option>
<mat-option [value]="option3">Option CC</mat-option>
<mat-option [value]="option4">Option DD</mat-option>
<mat-option [value]="option5">Option EE</mat-option>
</mat-select>
<div>
<textarea class="mention-reason" [(ngModel)]="reason" [ngModelOptions]="{standalone: true}"></textarea>
</div>
<button class="cancel" matDialogClose>Cancel</button>
<button class="submit" [mat-dialog-close] (click)="submitUserReason()">Submit</button></span>
dialog.ts
onSubmitReason = new EventEmitter();
submitUserReason(): void {
this.onSubmitReason.emit('hello');
}
onConfirmClick(): void {
this.dialogRef.close(true);
}
parent.ts
callSupport() {
const dialogRef = this.dialog.open(customerSupportComponent);
const subscribeDialog = dialogRef.componentInstance.onSubmitReason.subscribe((data) => {
console.log('dialog data', data);
//i can see 'hello' from MatDialog
});
dialogRef.afterClosed().subscribe(result => {
subscribeDialog.unsubscribe();
});
Thanks so much whoever helping.
I assume there are two buttons. 1) submit 2) close
So, If you want selected data in parent component on submit button click then,
submitUserReason(): void {
this.dialogRef.close({ option: userSelectedOption, reason:userReason });
}
And in parent component,
dialogRef.afterClosed().subscribe(result => {
console.log(result);
});
In your dialog.ts you want to pass your selected option instead of just a string. Something like:
submitUserReason(): void {
this.onSubmitReason.emit(selectedIssue);
}
You can emit whatever you want (depending on how you've typed things) so if you'd like to pass more data you could also pass an object:
submitUserReason(): void {
let data = { issue : selectedIssue, reason: userReason};
this.onSubmitReason.emit(data);
}
I have a select dropdown which needs to be prepopulated with the array of objects. However, the ngModel doesn't bind with the data. I've tried to demonstrate it in simpler way, but in reality, I have a home page, when I navigate from home page to contact Page, I need to populate the form data. If I directly enter the contact page, the form must be empty.
In the below example, to make it simpler, I am populating the data on page load, but issue I am facing is similar to my former example, ngModel of select doesn't get updated with data from JSON.
component:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular';
adViewList: AdViewModel = new AdViewModel();
callListType = [
{
callType: {
'id': 1,
'description': 'description1',
}
},
{
callType: {
'id': 2,
'description': 'description2',
},
}
]
compareByOptionId(idFist, idSecond) {
return idFist && idSecond && idFist.callType.description === idSecond;
}
prepopulateData = {
inciNum: "12365",
callType: {
'id': 2,
'description': 'description2',
}
}
ngOnInit() { // on page load
const someDate:any = this.prepopulateData;
this.adViewList = someDate
}
}
export class AdViewModel {
inciNum: string;
callType = Availability;
}
export class Availability {
id: string;
description: string;
}
HTML:
<input type="text" id="inputcomplaint" aria-describedby="complaintHelp"
autocomplete="new-password" name="complaint"
[(ngModel)]="adViewList.inciNum" #complaint="ngModel">
<select [(ngModel)]="adViewList.callType.description"
[compareWith]="compareByOptionId">
<option style="display:none"></option>
<option *ngFor="let data of callListType" [ngValue]="data">
{{data.callType.description}} </option>
</select>
Demo
I tested the following and it seems to work. Notice the change. I'm mapping [(ngModel)]="adViewList.callType" instead of [(ngModel)]="adViewList" because, callListType is a list of callType not AdViewModel
Also note, I'm printing the <p> List: {{adViewList | json}} </p> to check if the model is getting updated and it seems to be getting the values from what is selected.
Update: To have the default value appear as per the onNgInit() you have, you need to change ngValue to [ngValue]="data.callType" and the compare function needs to check ids, not objects. See code below.
Try this:
In your template:
<select name="select" [(ngModel)]="adViewList.callType"
[compareWith]="compareByOptionId">
<option style="display:none"></option>
<option *ngFor="let data of callListType" [ngValue]="data.callType">
{{data.callType.description}} </option>
</select>
In the component:
compareByOptionId(idFist, idSecond) {
return idFist.id === idSecond.id
}
PS: Sorry, I do not have a stackblitz id to share what I tested, I just edited your files to test.
You have 2 problems:
ngValue is invalid, change it to [value]
your binding is wrong, you need to match the [(ngModel)]="someValue" to the [value]="someValue" as it is your model id.
SOLUTION: Change [ngValue]="data" to [value]="data.callType.description" like this:
<select name="select" [(ngModel)]="adViewList.callType.description" [compareWith]="compareByOptionId">
<option style="display:none"></option>
<option *ngFor="let data of callListType" [value]="data.callType.description">
{{data.callType.description}}
</option>
</select>
NOTE: I suggest you to work with the id property instead of the description property as value like this:
[(ngModel)]="adViewList.callType.id"
[value]="data.callType.id"
I am making angular 6 application where i am using angular reactive form.
Html:
<form [formGroup]="form">
<h2>Click the add button below</h2>
<button (click)="addCreds()">Add</button>
<div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
<div [formGroupName]="i" style="display: flex">
<select (ngModelChange)="changeAction($event)" formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}"> {{option.value}} </option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="showLabel">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
</form>
<pre>
{{ form ?.value | json }}
</pre>
Ts:
form: FormGroup;
showLabel: boolean = false;
options : any = [
{ "key" : "set", "value": "Set" },
{ "key" : "wait", "value": "Wait" },
{ "key" : "go", "value": "Go" }
]
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
credentials: this.fb.array([]),
});
}
addCreds() {
const creds = this.form.controls.credentials as FormArray;
creds.push(this.fb.group({
action: '',
name: '',
label: ''
}));
}
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
Working stackblitz: https://stackblitz.com/edit/angular-form-array-example-yksojj
In this given example there will be an add button, upon clicking that button you will get a select-box with values as set,wait,go and an input called name.. Upon click over add button the same row will be added and forms each object inside array.
Also you can see an if condition inside html for label,
<div *ngIf="showLabel">
<input placeholder="Label" formControlName="label">
</div>
The thing i am in the need was in the select box if i choose set or go then the label needs to displayed otherwise it should not be displayed for which i have written,
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
To be clear enough If the user clicks three times add button then the dropdown and name field alone needs to be displayed for three times whereas if the user selects the value from dropdown as set or go then the label input needs to be displayed to that particular row alone where the dropdown has the value set and go.. If the selection was wait then there should not be label box for the row which has dropdown value as wait.
Kindly help me to achieve the expected result..
If you add disabled:true property to formControl it will exclude fromControl from formGroup then you can enable formControl manually
creds.push(this.fb.group({
action: '',
name: '',
label: {disabled:true, value: ""}
}));
Then enable using enable method
changeAction(e,index) {
if(e === "set" || e === "go") {
this.showLabel = true;
this.form.get('credentials').at(index).get('label').enable();
} else {
this.showLabel = false;
this.form.get('credentials').at(index).get('label').disable();
}
}
Example:https://stackblitz.com/edit/angular-form-array-example-5buwyr
Please, NOT use (ngModelChange) or changeAction($event) to get the value of a control in an array -well, ngModelChange is for Template driven form, not for Reactive Form.
First change your form, create the form using a div with formArrayName="credentials", and a inner div *ngFor="let creds of form.get('credentials').controls
<!--a div with formArrayName--->
<div formArrayName="credentials" >
<!--a div *ngFor over "controls", in this div don't forget formGroupName-->
<div *ngFor="let creds of form.get('credentials').controls; let i = index"
[formGroupName]="i" style="display: flex">
<select formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}">
{{option.value}}
</option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="??????????????">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
Well, now the condition. To get the value of "action" you can use
form.get('credentials').at(i).value.action
//or, better
creds.value.action
So, your div becomes like
<div *ngIf="creds.value.action=='set' ||
creds.value.action=='go'">
<input placeholder="Label" formControlName="label">
</div>
This aproach avoid unnecesary code in your .ts.
this.showLabel
The scope of this variable is your whole component. Therefore, turning it on or off will show and hide all inputs.
You need a per-row value (creds.showLabel in your interface), or use this in your template :
*ngIf="['set','go'].includes(creds.action)"
Updated Stackblitz
By the way, this :
changeAction(e) {
if(e === "set" || e === "go") {
this.showLabel = true;
} else {
this.showLabel = false;
}
}
is more elegant written this way :
changeAction(e) {
this.showLabel = ['set','go'].includes(e)
}
or this way :
changeAction(e) {
this.showLabel = e in ['set','go']
}
Well, Jeremy's answer is pretty nice and enforces to use most of the native apis given by platform/language, however, here is a traditional approach to understand what is the actual flow and scope of objects, etc,etc...
Root Cause: The show hide field is made global to the scope of component, not at per formgroup level. So changing a single value, and using for all, will affect to all.
Solution:
Use Jeremy's answer for clean coding and less error prone code.
Manage an array of variable that will take care of sho/hide detail for each form group.
In the below answer, added some comments for easy understanding, and added console.log to see what exactly happening. Also, played with index i created in *ngFor and showed how you can make use of these things in future.
import { Component } from '#angular/core';
import { FormControl, FormGroup, FormArray, FormBuilder } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: 'app.component.html',
})
export class AppComponent {
form: FormGroup ;
showLabel = [];
creds : FormArray;
options : any = [
{ "key" : "set", "value": "Set" },
{ "key" : "wait", "value": "Wait" },
{ "key" : "go", "value": "Go" }
]
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
credentials: this.fb.array([]),
});
this.creds = this.form.controls.credentials as FormArray;
}
addCreds() {
this.creds.push(this.fb.group({
action: '',
name: '',
label: ''
}));
// every time adding new foem grp, adding lable show/hide data also.
this.showLabel.push(false);
}
changeAction(e, i) {
//every time input clikced, managing show/hide for that particular data also
console.log("iii", i, e.target.value);
if( e.target.value == "set" || e.target.value == "go") {
this.showLabel[i] = true;
} else {
this.showLabel[i] = false;
}
}
}
<form [formGroup]="form">
<h2>Click the add button below</h2>
<button (click)="addCreds()">Add</button>
<div formArrayName="credentials" *ngFor="let creds of form.controls.credentials?.value; let i = index">
<div [formGroupName]="i" >
<select (change)="changeAction($event, i)" formControlName="action">
<option *ngFor="let option of options" value="{{option.key}}"> {{option.value}} </option>
</select>
<input placeholder="Name" formControlName="name">
<div *ngIf="showLabel[i]">
<input placeholder="Label" formControlName="label">
</div>
</div>
</div>
</form>
<pre>
{{ form ?.value | json }}
</pre>
See live stackblitz working code
Note: Traditional is meant here.. as we every time do i.e. handle our problem on our own and create new problem on us to solve. It's not a tradition. :P
I have two component
My first component (parent component) like this :
<template>
<div>
...
<form class="form-horizontal" id="form-profile" #submit.prevent="submitFormProfile">
...
<form-select id="province" name="province" v-model="province" :options="provinces" v-on:triggerChange="changeProvince" :is-required="isRequired" model="1">Province</form-select>
<form-select id="regency" name="regency" v-model="regency" :options="regencies" v-on:triggerChange="changeRegency" :is-required="isRequired" model="1">Regency</form-select>
<form-select id="district" name="district" v-model="district" :options="districts" v-on:triggerChange="changeDistrict" :is-required="isRequired" model="1">District</form-select>
....
</form>
...
</div>
</template>
<script>
export default {
data() {
return {
province: null,
regency: null,
district: null,
}
},
mounted() {
...
this.getUserLogin()
.then((response) => {
let user = response.data
this.province = user.address_list.province
this.regency = user.address_list.regency
this.district = user.address_list.district
})
.catch(error => {
console.log(error)
});
},
...
}
</script>
My second component (child component) like this :
<template>
<div class="form-group">
<label :for="id" class="col-sm-3 control-label"><slot></slot></label>
<div class="col-sm-9">
<select :id="id" :name="name" class="form-control" v-model="selected" v-on:change="applySelected">
<option disabled value="">Please select one</option>
<option v-for="option in options" :value="option.id">{{option.name}}</option>
</select>
</div>
</div>
</template>
<script>
export default {
props: ['value', ...],
data() {
return {
selected: this.value|| ''
}
},
mounted() {
console.log(this.value)
}
...
}
</script>
When the components executed, it will call ajax to get value province, regency, district
If I console.log(response.data) in the response ajax (parent component), I get the value
But if I console.log(this.value) in the second component, I don't find the value. The value = null
This seems to happen because when calling child component, the ajax process is not finished yet
How can I solve this problem?
Option 1 - Add a watcher
The problem is you only set selected: this.value || '' in your data when the component is initialized. To have it update, you need to add a watcher too, like so:
watch: {
value(newValue) {
this.selected = newValue;
}
}
This will update the selected prop in child each time the one in parent changes.
Option 2 - Refer directly to the prop
You can get rid of selected altogether and just use value in the child. It will always remain up to date with the parent.