angular 2 validation on dynamic generated fields in loop - html

I have a list of input fields that are generated with a model. I am trying to add validation to them.
The requirement is they should not be empty or less than 2 characters.
problem is in the documentation only shows validation with non-dynamically generated variable Name. My fields are all generated dynamically. So there is no tempVariableName I can hardcode (otherwise they conflict), so I created temp variable from the name of the property I binded the field to. So I came up with something like this :
<div *ngFor="let field of connector.configFields">
<label>{{field.name}}</label>
<input [(ngModel)]="field.value" [type]="field.name === 'Password' ? 'password' : 'text'"
placeholder="{{field.name}} (required)"
ngControl="[fieldName+field.name]"
required minlength="2"
#fieldName+[field.name]="ngModel" />
<div *ngIf="(fieldName+[field.name]).errors && ((fieldName+[field.name]).dirty || (fieldName+[field.name]).touched)">
<span *ngIf="(fieldName+[field.name]).errors.required">Enter Name</span>
<span *ngIf="(fieldName+[field.name]).errors.minlength">Name minimum at 2 characters</span>
</div>
</div>
and the configFields in typescript look like this :
export class FieldModel {
public name: string;
public type: string;
public value: any;
}
But this simply would not work. I am new to angular 2 so I am not exactly sure what I did wrong.

You can use the unique index for each field in the array. Use this together with the name attribute (and ngModel) which will evaluate each form controls separately. So each input field gets the unique name, eg:
name="f{{i}}"
where we get {{i}} from the iteration:
<div *ngFor="let field of connector.configFields; let i = index">
So finally, your template could look like this:
<form #myForm="ngForm">
<div *ngFor="let field of connector.configFields; let i = index">
<input name="f{{i}}" [(ngModel)]="field.value" [type]="field.name === 'Password' ? 'password' : 'text'" required #f="ngModel" minlength="2"/>
<div *ngIf="f.errors && (f.dirty || f.touched)">
<div *ngIf="f.errors.required"> This field is required </div>
<div *ngIf="f.errors.minlength"> Min 2 chars </div>
</div>
</div>
</form>
Here's a live
Demo

Prepare data in model and return to angular. Angular and hard logic in the template = bad friends.

But if you have a select option and if has *ngFor for option then error message loses its mapping, due second *ngFor loop
better to define custom class for error message and use css display: none or **block*
.custom-valid-box{
display: none;
}
.form-control-danger + .custom-valid-box{
display: block;
}

Related

Angular data bind on div not working on update list value

I have this div that I need to take the text value from to use in my doubleClicks function. Since ngmodel does not work on div, I tried to use the solution on this link (ngModel is not working on <div> tag using contenteditable and have html as an input)
I get the error "object is possibly null" when using "event.target.textContent".
After the list is loaded I need to be able to update any value from the list, but for that, I need the input text from the user in the div id=newProjectName to pass to the backend service.
<li
*ngFor="let i of listing; let idx = index; let isLast = last"
class="company"
(click)="selectedLine($event, idx)"
(dblclick)="doubleClicks($event, idx)"
[ngClass]="{ selected: idx == selectedItem }"
[ngClass]="{ lastAdded: isLast && isProjects && newProjectEntry }"
id="listing"
>
<div
*ngIf="!isProjects"
class="name"
id="newProjectName"
name="newProjectName"
[innerHTML]="content"
(input)="contentNew = $event.target.textContent"
>
{{ i.name }}
</div>
</li>
doubleClicks(event, idx) {
if (this.isProjects) {
// USE DIV (Id=newProjectName) TEXT VALUE HERE
name = ....
}
}

Angular - How to display single "no results" message on no results

I'm having trouble coming up with a way to show my "no results" div element. Basically, I have a list component containg order timeline section components, each one of these section contains order components. Like so:
My orders-list.component.html (check bottom div):
<div class="list-container" [ngClass]="{section: isDeliverySlotsActive === false}">
<label class="list-header" *ngIf="isDeliverySlotsActive === true" style="margin-top: 1.625rem">DELIVERY SLOTS ORDERS</label>
<div [ngClass]="{section: isDeliverySlotsActive === true}" *ngFor="let date of timelines">
<app-orders-list-section
[orders]="orders"
[timeline]="date"
[isDeliverySlotsActive]="isDeliverySlotsActive"
[searchTerm]="searchTerm"
></app-orders-list-section>
</div>
</div>
/* I want to show the below div when there are no results for the search */
<div id="no-results">
<img src="../../../assets/my-orders/no-results.png" alt="No Results" style="margin-top: 6.063rem; margin-bottom: 2.837rem;">
<label class="no-results-text">COULDN'T FIND ANYTHING</label>
<label class="no-results-text weight-medium">Search by order number or customer</label>
</div>
For each section, a filtering method is applied when the user searches for an order using the search bar. If the search term does not correspond to an order in a section, the order is not displayed for that section. If there are no results for that section the section header is also not displayed.
My orders-list-section.component.html:
<div *ngIf="filteredSectionOrders.length > 0">
<label
*ngIf="isDeliverySlotsActive === true"
[ngClass]="{ slots: isDeliverySlotsActive === true }">
{{ timeline | addSectionDateFormat }}
</label>
</div>
<div *ngFor="let order of filteredSectionOrders">
<app-orders-list-item
[order]="order"
[timeline]="timeline"
></app-orders-list-item>
</div>
My filter method in the section component:
filterSectionOrders(searchString: string){
if(!searchString) return;
if(this.hasNumbers(searchString)){
this.filteredSectionOrders = this.filteredSectionOrders.filter(order => order.order_num.toString().indexOf(searchString) !== -1);
}
else{
this.filteredSectionOrders = this.filteredSectionOrders.filter(order => {
if(order.first_name && order.last_name){
let fullName = order.first_name + " " + order.last_name;
if(fullName.toLowerCase().indexOf(searchString.toLowerCase()) !== -1){
return order;
}
}
})
}
}
Given that I apply this filter to each section and not to the list as a whole, how can I find out when there are 0 total results so I can show only one (not for each section) div element with a "no results found" message?
Thank you in advance.
You can easily use *ngIf;else link to ngIf from angular inside your HTML
I am not sure where do you use filteredSectionOrders, because it is not shown in your html, but let's assume your app-orders-list-section has some HTML logic where you use *ngFor to loop through orders and show it properly
so, I guess your code looks something like this
<div class="order" *ngFor="let order of filteredSectionOrders">
<img/>
<p>
{{ order.first_name + ' ' + order.last_name }}
</p>
</div>
This is simplified html how I assume it looks like.
What you can do is next:
<ng-template *ngIf="filteredSectionOrders.length > 0; else noResultsBlock">
// here you insert your code to render orders
<div class="order" *ngFor="let order of filteredSectionOrders">
<img/>
<p>
{{ order.first_name + ' ' + order.last_name }}
</p>
</div>
</ng-template>
<ng-template #noResultsBlock>
<p> No results </p>
</ng-template>
So, this would simple solution
If you want to improve it even more, it would be better to have a new variable, lets say areThereResults, which you will set to true or false, at the end of your method filterSectionOrders, based on filterSectionOrders.length. Then, you would use this new variable inside *ngIf check, instead of filterSectionOrders.length > 0.
Reason for using boolean variable instead of using actual array is detection changes, and will anguar re-render UI inside *ngIf. You can read more about it on Angular documentation, just search for detection changes.

Angular 11 fill input in HTML

I'm facing a problem, mainly I cannot fill a value in input but in the HTML. I searched but nothing seems to work. I tried value, (value), [(value)], "k.bag.ConstructionYear", "{{k.bag.ConstructionYear}}" and other combinations and nothing seems to work.
mycomponent.html
<ng-container
*ngIf="{
// other stuff
firstStepState: firstStepState$ | async
} as o"
>
//........
<ng-container *ngIf="{
bag : testService.getData(o.firstStepState.value1, o.firstStepState.value2) | async
} as k">
<ng-container *ngIf="k.bag !== undefined && k.bag !== null">
{{k.bag.ConstructionYear}} // <-- this is showing
<input
type="text"
name="constructionYear"
formControlName="constructionYear"
id="constructionYear"
data-test="constructionYear"
placeholder=""
[value]="k.bag.ConstructionYear" <-- this is the problem
/>
by assigning formControlName="constructionYear" to the input, you delegate value control of this input to your formControl. from this point of time you should update the logical form from your ts code whenever you want, not the value of the input. for example through method control.setValue(newValue)
<input
type="text"
name="constructionYear"
formControlName="constructionYear"
id="constructionYear"
data-test="constructionYear"
placeholder=""
[(ngModel)]="k.bag.ConstructionYear"
/>
for binding with your data

ng2-ckeditor set value by typescript

I have created two ckeditor with *ngfor:
<div class="form-group" *ngFor="let lang of languages">
<span>Texto legal en {{lang.translate}} {{lang.abbr}}</span>
<ckeditor id="{{lang.abbr}}" formControlName="{{lang.abbr}}" [config]="config" (change)="onChange($event, lang.abbr)" (ready)="onReady($event)" (focus)="onFocus($event)" (blur)="onBlur($event)" debounce="500">
</ckeditor>
</div>
Languages is an array with object like:
{abbr: "es", translation: "IDIOMA.es"}
If I compile this, it gets the following error:
There is no FormControl instance attached to form control element with name: 'en'
If I try to remove formControlName="{{lang.abbr}}", it works but doesn't appear to save any data.
In component, I have the following:
let group = {
'porcentaje': [setup.porcentaje || '', Validators.compose([Validators.required, Validators.minLength(1)])]
}
for (let lang of this.languages) {
group[lang.abbr] = [setup[lang.abbr] || '']
}
this.formRegister = this.fb.group(group);
Thank you!
Did you import the package correctly?
https://github.com/chymz/ng2-ckeditor
According to the documentation, it is not possible to assign id and formControl. Try to replace the id with ngModel.
<div class="form-group" *ngFor="let lang of languages">
<span>Texto legal en {{lang.translate}} {{lang.abbr}}</span>
<ckeditor [config]="config" (change)="onChange($event, lang.abbr)" (ready)="onReady($event)" (focus)="onFocus($event)" (blur)="onBlur($event)" debounce="500">
<p id="{{lang.abbr}}">Try to use your Validators here!</p>
</ckeditor>
</div>

How to show 1 element in an array using ngFor Angular2

On my website if I have more than one element in my array. My template looks like this.
I want to have a button to go to the next element of this array and only display one set of data and use the button to control which element of the array the user sees.
My current code looks like this:
<div class='panel-body' *ngIf ='case'>
<h3> Details </h3>
<div id="left-side" *ngFor="let tag of case?.incidents ">
<p>Date: <span class="name">{{tag.date}}</span> </p>
<p>DCU: <span class="name">{{tag.dcu}}</span></p>
<p>Location:<span class="name"> {{tag.location}}</span> </p>
</div>
I was thinking of using some sort of index or an ng-container or some work around using ngIf or ngFor. I am unsure of how to implement this.
All help would be greatly appreciated!
You're not going to need an ngFor or ngIf in this situation. What you'll want is a variable to keep track of the user's index, and then a function that changes that index.
<h3> Details </h3>
<div id="left-side" >
<p>Date: <span class="name">{{case?.incidents[userIndex].date}}</span> </p>
<p>DCU: <span class="name">{{case?.incidents[userIndex].dcu}}</span></p>
<p>Location:<span class="name"> {{case?.incidents[userIndex].location}}</span> </p>
</div>
<button (click)="changeIndex(-1);">Previous</button>
<button (click)="changeIndex(1);">Next</button>
and in your component.ts you'll have:
userIndex = 0;
changeIndex(number) {
if (this.userIndex > 0 && number < 0 || //index must be greater than 0 at all times
this.userIndex < this.case?.incidents.length && number > 0 ) { //index must be less than length of array
this.userIndex += number;
}
This will be a standard for in-view paging systems for other projects as well.
To achieve this you can use angular's default SlicePipe like this example,
#Component({
selector: 'slice-list-pipe',
template: `<ul>
<li *ngFor="let i of collection | slice:1:3">{{i}}</li>
</ul>`
})
export class SlicePipeListComponent {
collection: string[] = ['a', 'b', 'c', 'd'];
}
You can find more details here