I have a custom dropdown in which i need to update or set default value using formControlName.
I am using ControlValueAccessors in the shared component so that I can attach formControls to them in parent component and update the formControl values of the form.
Right now I have a issue in setting the default value by using the below code.
this.parentForm = this.fb.group({
district: ['bangalore', Validators.required], // bangalore should be set as my default value.
distance: [''],
state:['']
});
HTML code :
<form [formGroup]="parentForm">
<app-common-dropdown placeHolder="select district" [dropDownId]="'districtLabel'" [dataList]="['bangalore','chennai','pune']" formControlName="district" ></app-common-dropdown>
<app-common-dropdown placeHolder="select distance" [dropDownId]="'distanceLabel'" [dataList]="[100,200,300,400]" formControlName="distance" ></app-common-dropdown>
<app-common-dropdown placeHolder="select state" [dropDownId]="'stateLabel'" [dataList]="['karnataka','tamil nadu','mumbai']" formControlName="state" ></app-common-dropdown>
I have attached the example code for this https://stackblitz.com/edit/angular-p2gvtm. Kindly look into the demo code and help me as I am feeling that code is being written more for just setting and getting values using formcontrols.
Hello here is a stackblitz with refactored fork of your code.
First thing first if you want to display the value of the control you must pass it to the place where you are going to visualize it.
app.component
<form [formGroup]="parentForm">
<app-common-dropdown [controlForDisplay]="parentForm.get('city')"
placeHolder="select district"
[dataList]="['bangalore','chennai','pune']"></app-common-dropdown>
<app-common-dropdown [controlForDisplay]="parentForm.get('state')"
placeHolder="select distance"
[dataList]="[100,200,300,400]"></app-common-dropdown>
<app-common-dropdown [controlForDisplay]="parentForm.get('country')"
placeHolder="select state"
[dataList]="['karnataka','tamil nadu','mumbai']"></app-common-dropdown>
</form>
<button type="submit" (click)="getFormValues()">submit</button>
In your case I had added new input to your app-common-dropdown, called controlForDisplay in order to pass reference of the desired formControl to the component. I also deleted the dropdownId, the reason for that action I will explain latter on.
common-dropdown.component.html
<div [ngClass]="{'cs-active': dropdownOpen}"
class="cs-select cs-skin-border"
tabindex="0">
<span (click)="selectClicked($event)" class="cs-placeholder">
{{!!controlForDisplay.value ? controlForDisplay.value : placeHolder }}
</span>
<div class="cs-options">
<ul>
<li *ngFor="let item of dataList" (click)="selectOption(item)">
<span>{{item}}</span></li>
</ul>
</div>
</div>
So now we are heading to the common-dropdown.component.html, where the important part is the following line
{{!!controlForDisplay.value ? controlForDisplay.value : placeHolder }}
Now through the added controlForDisplay input we can access the formControl reference that is holding the desired default value of the dropdown and visualize it if there is any default value or display the placeholder if the form control is empty.
commpon-dropdown.component.ts
#Component({
selector: 'app-common-dropdown',
templateUrl: './common-dropdown.component.html',
styleUrls: ['./common-dropdown.component.css']
})
export class CommonDropdownComponent {
#Input() placeHolder: string;
#Input() dataList: any;
#Input() controlForDisplay: FormControl = new FormControl()
dropdownOpen = false;
selectClicked(event: any) {
this.dropdownOpen = true
}
selectOption(value: string) {
this.controlForDisplay.patchValue(value)
this.closeDropDown('')
}
closeDropDown(event: any) {
this.dropdownOpen = false;
}
}
Here the major changes are that instead of using the native elements we are updating the formControl value trough the formControl API, called patch value, by doing so we are updating the whole form which is accessed both from the parent and the current component.
p.s.
You must add the CommonModule inside your app.module.
Pretty much that fixes your problem. Keep in mind that it's almost always preferable to use the Angular API-s when creating web pages with Angular instead the DOM API-s, I would suggest the you take the Tour of Heroes , which is the official angular tutorial.
Related
In an Angular reactive form, we have a checkbox which is required to be checked before our save button will work. Now, we have a new requirement to only display this checkbox to certain regions, which is easily accomplished via an environment property and an *ngIf on the checkbox.
However, how do I conditionally make that save button's [disabled] attribute not care about the checkbox?
my-component.component.html
<div *ngIf="isMyCheckboxRegion">
<input [formControl]="myCheckbox" type="checkbox" id="my-checkbox">
<label for="my-checkbox">Please check this box</label>
</div>
<div>
<button validationgroup="SaveMyForm"
(click)="onSubmit()" [disabled]="anotherFormGroup.invalid || myCheckbox.invalid">Save</button>
</div>
my-component.component.ts
import { environment } from 'src/environments/environment';
export class myComponent implements OnInit {
public isMyCheckboxRegion = environment.isMyCheckboxRegion;
public myCheckbox = FormControl('', Validators.requiredTrue);
}
Try setting Validators.requiredTrue conditionally:
public myCheckbox = FormControl('', this.isMyCheckboxRegion ? Validators.requiredTrue : undefined);
As the validator is applied conditionally, myCheckbox.invalid should be false regardless of the checkbox's value.
Alternative:
You can also use isMyCheckboxRegion in the HTML:
<button
validationgroup="SaveMyForm"
(click)="onSubmit()"
[disabled]="anotherFormGroup.invalid || (isMyCheckboxRegion && myCheckbox.invalid)"
>Save</button>
im making some register pages in a Ionic-Angular project, and i use some buttons to go to the next requisites to register. For example: first I ask for email, and until email format isnt't correct, my button to go to next page is disabled. When correct I enable it and go to a page where your name is asked. Here when the user enters his name, my code sets disabled atribute of the button to false, but the button stays as it was. As if i cant access the disable attribute. But when i reload the page in my browser, that same button does what i say in the code (disable = false).
This is very strange for me, how can the same code I used in the page before for the button disable work, but in the next page when I navigate by url to that next page not work. But that button on the page does work when reloaded in browser.
I´ve been thinking it could be something of angular lifecycle of the view|page|component.
I leave my code here, but i really think its just fine, and its doms or angulars fault.
HTML:
<div class="content" id="body">
<p class="titulo">Mi nombre es</p>
<ion-input
id="ionInput"
class="input"
inputmode="text"
maxlength="25"
minlength="2"
mode="ios"
pattern="text"
required="true"
type="text"
(ionChange)="inputValueChange($event)"
></ion-input>
<ion-button
disabled
id="continueButton"
(click)="navigateForward()"
expand="block"
fill="solid"
shape="round"
class="buttonContinue"
>Continuar</ion-button
>
</div>
Dont really take in count the CSS classes i remove or add. It has nothing to do with the problem.
.ts FILE:
export class Step2NamePage implements OnInit {
name = '';
continueButton: HTMLButtonElement;
constructor(private router: Router) {}
ngOnInit() {
}
inputValueChange(ev) {
const input = document.getElementById('ionInput');
const continueButton = document.getElementById('continueButton') as HTMLButtonElement
this.name = ev.detail.value;
// console.log(this.name);
if (this.name.length > 2) {
if (input.classList.contains('input')) {
(document.getElementById('continueButton') as HTMLButtonElement).disabled = false;
console.log('lo desbloque');
console.log(input, this.continueButton);
input.classList.remove('input');
input.classList.add('inputFull');
}
} else {
if (input.classList.contains('inputFull')) {
input.classList.remove('inputFull');
input.classList.add('input');
(document.getElementById('continueButton') as HTMLButtonElement).disabled = true;
console.log(input, this.continueButton);
}
}
}
navigateForward() {
this.router.navigate(['/register/step3-birthday']);
}
}
Angular keeps track of the DOM in it's own special way, and provides
ample ways for you to access element properties, values etc. You
shouldn't hardly ever be trying to find elements with
document.getElementById and the like...
Use ngModel (import the forms module - see this post) to track and utilize form input values
Use ngClass to have the DOM update classes for elements on the fly - see this post
export class Step2NamePage implements OnInit {
name = '';
continueButton: HTMLButtonElement;
constructor(private router: Router) {}
ngOnInit() {}
navigateForward() {
this.router.navigate(['/register/step3-birthday']);
}
}
<div class="content" id="body">
<p class="titulo">Mi nombre es</p>
<ion-input
id="ionInput"
*ngClass="{'inputFull':name.length>2, 'input': name.length<=2}"
[(ngModel)]="name"
inputmode="text"
maxlength="25"
minlength="2"
mode="ios"
pattern="text"
required="true"
type="text"
></ion-input>
<ion-button
[disabled]="name.length<=2"
id="continueButton"
(click)="navigateForward()"
expand="block"
fill="solid"
shape="round"
class="buttonContinue"
>Continuar</ion-button>
</div>
I have an input field and add button in a component.
Button is disabled at first, when user enters valid format url, button becomes active, then button add this domain to db, refresh table(which is in another component). I want to achieve that, after button is clicked it makes input field empty (refresh like page is just opened)
My component template is
<form #addDomainForm="ngForm" (ngSubmit)="addClicked()">
<mat-card class="topia-box-shadow">
<mat-card-content fxLayout="row wrap" fxLayoutAlign="left" fxLayoutGap="16px">
<div>
<p>Enter domain name or copy and paste the full link to add its domain
to white list</p>
<mat-form-field>
<mat-label>Enter link here</mat-label>
<input type="url" matInput [(ngModel)]="domainName" #url="ngModel"
[class.is-invalid]="url.invalid && url.touched"
name="url" [pattern]="urlValidationRegexPattern" required>
</mat-form-field>
<mat-error *ngIf="url.invalid && (url.dirty || url.touched)">
Please enter the link in correct format
</mat-error>
</div>
</mat-card-content>
<button type="button" id="search" class="topia-btn topia-primary-btn action-buttons"
[disabled]="!addDomainForm.valid" (click)="addClicked()">Add
Domain
</button>
</mat-card>
</form>
My ts file for that component:
export class DomainWhiteListingAddComponent implements OnInit {
#Output()
domainAdded = new EventEmitter<true>();
domainName: string;
public urlValidationRegexPattern = /^((((https?)(:\/\/))?((www)\.)?)?|mailto:(\w+\.?\w+)\#)([a-z0-9]+\.[a-z0-9]+)+((\/([\w#]+|[\w#]+(([.\w#])*(-\w+|=\w+|\?\w+=\w+(&\w+=(\w)+)*)?)+)+))*$/;
constructor(private globalSettingService: GlobalSettingsService, private dialog: MatDialog) {
}
ngOnInit() {
}
addClicked() {
const newDomainModel = {
id: null, domain: this.domainName, disabled: false
} as Domain;
this.globalSettingService.addDomainToList(newDomainModel)
.subscribe((data: Domain) => {
this.domainAdded.emit(true);
}, (error: HttpErrorResponse) => {
this.showErrorDialog(error.error);
});
}
}
This code is working but after clicking button and adding the element, input box value still stays there.
if i add a single line code to .ts file addClicked() method
this.domainName = null;
this time, input field becomes empty as I wanted but it appears as invalid so it shows error. I just want it make as default like the page is opened at beginning. Like
But it shows as:
How can I do that ? (if it is needed i can add parent component codes and other component code also)
Or if it is needed; how can i reset just that component?
How about
#ViewChild(addDomainForm) formDirective: FormGroupDirective;
addClicked() {
this.formDirective.resetForm();
}
It'll reset your form as you are checking if the form is dirty
You are making the ngModel domainName as null so it throws error.
Change it like this.domainName = ''
Along with setting the ngModel to it's original value, empty or null. You should also set your form asPristine.
If you are using Template-Driven forms and you have something like this in your component: #ViewChild('myForm') myform: any;
You can use the markAsPristine() function on the form property of your form. So it would be this.myform.form.markAsPristine().
In your HTML you use url.invalid && url.touchedto add the invalid red class, perhaps try this instead:
[class.is-invalid]="url.invalid && url.dirty"
This way it's not flagged as invalid until the value as actually changed.
First of all, I don't see how the modal could have anything to do with this issue since its actually in this component's code, not a child. Still, this is in a modal, just in case.
I'm opting to not use FormArray since I need to keep track of my selections that may be added in a separate object, so I'm just creating unique IDs for the controls and adding them to the FormGroup. I can access the controls in the ts code, set values, get values, etc, but the form isn't binding and I can't figure out why not.
I can have an unknown number of items in this modal form, which will each have a selector (dropdown to select a property) and then the input to be able to modify some data. The input could be of different types, so it needs to be added and binded upon the choice from the selector.
<form [formGroup]="multiEditFormGroup" novalidate>
<div *ngFor="let item of multiEditSelections; let i = index">
<div>
<mdb-select [options]="availablePropsSelect"
placeholder="Choose property to edit"
class="colorful-select dropdown-main"
(selected)="multiEditSelectProp(item.selectorId, $event)"></mdb-select>
<label>Property to edit</label>
</div>
<div>
<div>
<input mdbActive
type="text"
class="form-control"
[formControlName]="item.controlId" />
</div>
</div>
</div>
</form>
Excepts of ts code:
public multiEditFormGroup = new FormGroup({});
onModalOpen():void {
const selectorId = this.utils.uuid();
this.multiEditFormGroup.addControl(selectorId, this.propSelector);
this.multiEditSelections.push({
selectorId: selectorId,
prop: '',
label: '',
type: '',
controlId: '' // not yet known since type hasn't been chosen
});
}
onSelect(selectorId: string, selectEvent: any): void {
let selection = this.multiEditSelections.find(selection => {
return selection.selectorId === selectorId;
});
const controlId = this.utils.uuid();
const prop = selectEvent.value;
this.multiEditFormGroup.get(selection.selectorId).setValue(prop);
this.multiEditFormGroup.markAsDirty();
this.multiEditFormGroup.markAsTouched();
const model = this.multiEditModel.find(model => {
return model.prop === prop;
});
this.multiEditFormGroup.addControl(controlId, this.testCtrl);
selection.controlId = controlId;
selection.prop = prop;
selection.label = model.label;
selection.type = model.type;
}
Logging to console shows that items are being added to the FormGroup, but the binding isn't happening to the DOM. For example, I can add a (keyup) event handler to my input and set the value in the form control which has already been created, and the FormGroup is updated. However, any input added in the front-end doesn't update the FG since it obviously isn't binding. Is this a timing issue or because the controlId is being updated later? I'm creating the FormControl before updating my array that is being iterated.
Oh and I get no errors in console on this.
I think you need to make this change:
[formControlName]="item.controlId"
needs to be:
formControlName="{{item.controlId}}"
I have a code:
document.getElementById('loginInput').value = '123';
But while compiling the code I receive following error:
Property value does not exist on type HTMLElement.
I have declared a var: value: string;.
How can I avoid this error?
Thank you.
if you want to set value than you can do the same in some function on click or on some event fire.
also you can get value using ViewChild using local variable like this
<input type='text' id='loginInput' #abc/>
and get value like this
this.abc.nativeElement.value
here is working example
Update
okay got it , you have to use ngAfterViewInit method of angualr2 for the same like this
ngAfterViewInit(){
document.getElementById('loginInput').value = '123344565';
}
ngAfterViewInit will not throw any error because it will render after template loading
(<HTMLInputElement>document.getElementById('loginInput')).value = '123';
Angular cannot take HTML elements directly thereby you need to specify the element type by binding the above generic to it.
UPDATE::
This can also be done using ViewChild with #localvariable as shown here, as mentioned in here
<textarea #someVar id="tasknote"
name="tasknote"
[(ngModel)]="taskNote"
placeholder="{{ notePlaceholder }}"
style="background-color: pink"
(blur)="updateNote() ; noteEditMode = false " (click)="noteEditMode = false"> {{ todo.note }}
</textarea>
import {ElementRef,Renderer2} from '#angular/core';
#ViewChild('someVar') el:ElementRef;
constructor(private rd: Renderer2) {}
ngAfterViewInit() {
console.log(this.rd);
this.el.nativeElement.focus(); //<<<=====same as oldest way
}
A different approach, i.e: You could just do it 'the Angular way' and use ngModel and skip document.getElementById('loginInput').value = '123'; altogether. Instead:
<input type="text" [(ngModel)]="username"/>
<input type="text" [(ngModel)]="password"/>
and in your component you give these values:
username: 'whatever'
password: 'whatever'
this will preset the username and password upon navigating to page.
Complate Angular Way ( Set/Get value by Id ):
// In Html tag
<button (click) ="setValue()">Set Value</button>
<input type="text" #userNameId />
// In component .ts File
export class testUserClass {
#ViewChild('userNameId') userNameId: ElementRef;
ngAfterViewInit(){
console.log(this.userNameId.nativeElement.value );
}
setValue(){
this.userNameId.nativeElement.value = "Sample user Name";
}
}