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

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.

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 = ....
}
}

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

How to show/hide in Angular2

I have a component that show/hide element by clicking a button.
This is my html
<div *ngFor="let history of histories | sortdate: '-dateModified'">
<p><b>{{ history.remarks }}</b> - <i>{{history.dateModified | date:'short'}}</i></p>
<a href="google.com"
[class.datatable-icon-right]="history.$$expanded"
[class.datatable-icon-down]="!history.$$expanded"
title="Expand/Collapse Row"
(click)="toggleExpandRow(history)"></a>
<!-- hide/show this by clicking the button above.-->
<div *ngFor="let step of history.steps; let i = index">
<b>{{i+1}}.</b> {{step}}
<span class="clear"></span>
</div>
<hr />
</div>
and my .ts
toggleExpandRow(row) {
console.log('Toggled Expand Row!', row);
//row
return false;
}
trying to search but, can't find any same sample.
On jquery, I can do this, but on Angular2, I am having hard time to figure this.
There are two options:
1- You can use the hidden directive to show or hide any element
<div [hidden]="!edited" class="alert alert-success box-msg" role="alert">
<strong>List Saved!</strong> Your changes has been saved.
</div>
2- You can use the ngIf control directive to add or remove the element. This is different of the hidden directive because it does not show / hide the element, but it add / remove from the DOM. You can loose unsaved data of the element. It can be the better choice for an edit component that is cancelled.
<div *ngIf="edited" class="alert alert-success box-msg" role="alert">
<strong>List Saved!</strong> Your changes has been saved.
</div>
Use the ngIf in your repeated rows. Create a boolean property called showStep to indicate whether the row should be expanded or not.
<div *ngFor="let step of history.steps; let i = index" ngIf="history.showStep">
<b>{{i+1}}.</b> {{step}}
<span class="clear"></span>
</div>
Then, in your .ts file:
toggleExpandRow(history) {
history.showStep = !history.showStep
//note the same porperty of showStep that is used in your html
}
Extra:
In fact, to save a few lines of codes, you don't even need the toggleExpandRow function at all. You can do it inline in your html:
//other attributes omitted for brevity
<a (click)="history.showStep = !history.showStep">

angular 2 validation on dynamic generated fields in loop

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;
}

Use ng-model to add class to all previous childrens Angular JS

Here is my html code:
<div ng-repeat="(key, a) in items" data-id="{{ Id }}" class="item" id="{{Key}}" ng-click="item($event, key)">
<div class="bubble></div>
<p>
<span> {{ description }}</span>
</p>
</div>
This is the list of items. When we click on the item in the list - all previous elements are set as active (add class).
Here is how it's done:
$scope.item = function(event, key) {
var current;
if ( $(event.target).hasClass('bubble')){
current = $(event.target).closest('#'+ Key);
changeItem(current);
}
function changeItem(current){
$(current).addClass('active');
$(current).prevAll().addClass('active');
$(current).nextAll().removeClass('active');
}
};
Is it possible to use ng-model or something else to set the active value by default form json file? Mean, in json - we have item 3 - marked as active, so how could I add this value to the $scope.item as current? or probably use ng-model?
I have not tried it, but something like this should work.Assuming that the class has to be applied to ng-repeat div. Change your ng-repeat div to:
<div ng-repeat="(key, a) in items" data-id="{{ Id }}" class="item" id="{{Key}}" ng-click="markSelected($index)" ng-class="{'active':selectedIndex<$index}">
</div>
The ng-click call a method markSelected($index) on the controller that sets the currently selected item index. The ng-class uses the current index ($index) and the selectedIndex to determine what class to apply.
The final task is to implement the function which looks like:
$scope.markSelected=function(index) {
$scope.selectedIndex=index;
}
You should stop using jquery and start to think in a more angular way.
There is a directive ng-class that is used to add or remove classes
You can find more information here : https://docs.angularjs.org/api/ng/directive/ngClass
<div ng-repeat="(key, a) in items" data-id="{{ Id }}" class="item" id="{{Key}}" ng-click="item(key)">
<div ng-class="{active : a.active, inactive : a.inactive}"></div>
<p>
<span> {{ description }}</span>
</p>
</div>
$scope.item = function(key){
$scope.items[key].active = true;
$scope.items[key].inactive = false;
}