highlight word by start and end matches using angular 7 - html

I have a collection of array which is having datas like
[0]: username {kavya} secret {password}
[1]: lorem ipsem text data value
[2]: lorem {{lrm}} datas {{pp}}
I am using foreach to show this data in frontend with
<div *ngFor="let data of output;let i=index">
<div *ngIf="data.includes('{') || data.includes('{{');else isNotEdited;" >
<div class="variable-textarea" contenteditable="false" >
<span>
{{data | slice:' ' }}
</span>
</div>
</div>
<ng-template #isNotEdited>
<ngx-md>{{data}}</ngx-md>
</ng-template>
</div>
Here I achieved like 0,2 row of div will be editable and in case of 1st array is non-editable.
But I want to do like specific matches which word starts with { or {{ and that particular word needs to be highlight and editable.
Is there any option to do in this way
Thanks in advance.

You could split the data into words:
<div *ngFor="let data of arr">
<span *ngFor="let word of data.split(' ')">
<span *ngIf="word.indexOf('{') > -1;else isNotEdited;">
<span class="variable-textarea-2" contenteditable="true">
{{word | slice:' ' }}
</span>
</span>
<ng-template #isNotEdited>
<span class="variable-text-2" contenteditable="false">
{{word}}
</span>
</ng-template>
</span>
</div>
Check this Stackblitz example I made based on your code: https://stackblitz.com/edit/angular-pkg6i9

this is a performance nightmare, you don't want to be running this many functions in template, and your format isn't helping you. map your data ahead of time into a friendlier view model:
this.mappedOutput = this.output.map(data => {
const editable = data.includes('{'); // checking for doubles is redundant
return {
editable,
data: !editable
? data
: data.split(' ')
.map(word => ({
word,
editable: word.trim().startsWith('{') && word.trim().endsWith('}')
}))
};
})
run this whenever your output changes, then use it in template:
<div *ngFor="let data of mappedOutput;let i=index">
<div *ngIf="data.editable;else isNotEdited;" >
<div class="variable-text">
<ng-container *ngFor="let word of data.data">
<div *ngIf="word.editable; else wordTmp" class="variable-textarea inline" contenteditable="true" >
<span>{{word.word}}</span>
</div>
<ng-template #wordTmp>
{{word.word}}
</ng-template>
</ng-container>
</div>
</div>
<ng-template #isNotEdited>
<ngx-md>{{data.data}}</ngx-md>
</ng-template>
</div>
and adjust the styles by adding this to your css:
.variable-textarea.inline {
display: inline-block;
width: fit-content;
margin: 0;
}
here's an edited blitz: https://stackblitz.com/edit/angular-arayve?file=src/app/app.component.html

Related

Restructure the display of instantiated elements in *ngFor

I am currently just adding randomized numbers to an array and displaying them to the user.
<div *ngFor="let temp of randomIntArray; let i = index">
<div *ngIf="i == randomIntArray.length - 1">
This is the real random number {{ temp }}
<div>
<button (click)="addRandomValueIntoRandomIntArray()">
add random number
</button>
</div>
</div>
<div *ngIf="i != randomIntArray.length - 1">
{{ temp }}
</div>
</div>
I think I understand what's happening here as I am creating a new element on the DOM each time the user clicks : addRandomValueIntoRandomIntArray() as its increasing the length of the randomIntArray.
Due to the: *ngIf="i == randomIntArray.length - 1 this will always be the last element and will be always be displayed at the bottom. Is there any feasible way for me to to swap them around and have all the new elements created at the bottom going downwards instead? Below is an image of how it currently looks.
You can reverse the order of ngFor items using randomIntArray.slice().reverse()
and you need to change ngIf condition to i==0.
app.component.html
<div *ngFor="let temp of randomIntArray.slice().reverse(); let i = index">
<div *ngIf="i ==0">
This is the real random number {{ temp }}
<div>
<button (click)="addRandomValueIntoRandomIntArray()">
add random number
</button>
</div>
</div>
<div *ngIf="i != randomIntArray.length - 1">
{{ temp }}
</div>
</div>

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.

How do I make multiple rows out of my ngFor Data?

In looping through my data, I'd like to have the "app-system" components displayed in even rows across the screen. When I try to put it in a table and then in a row, of course it only creates one row rather than multiple rows with the data.
Here's an image of what its doing currently https://imgur.com/a/O0MjBES
I have to scroll down to see all 190 items in the row.
I've thought about creating another ngFor loop of rows to apply to the data, but I can't quite figure out how. I'm unable to apply the answers I've tried to research to my project due to my lack of experience.
Here's my template that grabs my "app-system" component and loops through all of the data. It currently shows in one LONG row down the page:
Here is the template for the app-system component that is being copied over, the buttons need to be split into seperate rows. The popup box opens when you click one of the buttons.
<div class="section-container">
<div class="section-container">
<table>
<div *ngFor="let system of systemList | paginate: { itemsPerPage: 190,
currentPage: p }; let i = index">
<app-system [systemInput]="system"></app-system>
</div>
</table>
<div class="pagination section-container card-deck">
<pagination-controls (pageChange)="p = $event"></pagination-controls>
</div>
</div>
```<div class='card mb-1 shadow button-background'>
<div class= "popup-box">
<popup #popup>
<p *ngIf="systemInput.AMStatus; else showAMInvalid"> Anti-Malware Version: Current</p>
<ng-template #showAMInvalid>
<p style="background-color:yellow; ">Anti-Malware Version: Needs Update</p>
</ng-template>
<p *ngIf="systemInput.BLStatus; else showBLInvalid">Bitlocker Status: Active</p>
<ng-template #showBLInvalid>
<p style="background-color:yellow; ">Bitlocker Status: Inactive</p>
</ng-template>
<p *ngIf="systemInput.FirewallRullStatus; else showFirewallRuleInvalid"> Firewall Rules: Applied</p>
<ng-template #showFirewallRuleInvalid>
<p style="background-color:yellow; ">Firewall Rules: Not Applied</p>
</ng-template>
<p *ngIf="systemInput.FirewallContentStatus; else showFirewallContentInvalid"> Firewall: Enabled </p>
<ng-template #showFirewallContentInvalid>
<p style="background-color:yellow; ">Firewall Status: Disabled</p>
</ng-template>
<p *ngIf="systemInput.SCCMStatus; else showSCCMStatusInvalid"> SCCM Status: Running</p>
<ng-template #showSCCMStatusInvalid>
<p style="background-color:yellow; ">SCCM Status: Not Running</p>
</ng-template>
<p *ngIf="systemInput.MSBaselineStatus; else showMSBaselineInvalid"> Microsoft Standards: Met</p>
<ng-template #showMSBaselineInvalid>
<p style="background-color:yellow; ">Microsoft Standards: Not Met</p>
</ng-template>
<p *ngIf="dateCheck() == true; else showDateInvalid"> Last Update: {{systemInput.UpdatedDate | date}}</p>
<ng-template #showDateInvalid>
<p style="background-color:yellow; ">Last Update: {{systemInput.UpdatedDate | date}}</p>
</ng-template>
<p *ngIf="systemInput.USBStatus; else showUSBInvalid">Removable Storage: Disabled</p>
<ng-template #showUSBInvalid>
<p style="background-color:yellow; ">Removable Storage: Enabled</p>
</ng-template>
</popup>
</div>
<button (click)="ClickButton()" [ngClass]="{
'btn buttonGood':isValid(),
'btn buttonDateAlert':isValid()=='datewarning',
'btn buttonAlert':isValid()==false
}">{{systemInput.hostname}}</button>
</div>
export interface Ipcidata {
id: Int16Array;
hostname: string;
AMStatus: boolean;
BLStatus: boolean;
FirewallRullStatus: boolean;
FirewallContentStatus: boolean;
SCCMStatus: boolean;
MSBaselineStatus: boolean;
UpdatedDate: Date;
USBStatus: boolean;
}
You're saying row, but you're thinking of column. You should use flex/grid for that.
You should do what you're trying with css.
On container element do:
.container {
display: flex;
flex-wrap: wrap;
flex-direction: column;
width: 500px; // Or whatever
height: 500px; // Or whatever
}
Try changing the <div> to a <tr> element
<div class="section-container">
<table>
<tr *ngFor="let system of systemList | paginate: { itemsPerPage: 190,
currentPage: p }; let i = index">
<app-system [systemInput]="system"></app-system>
</tr>
</table>
<div class="pagination section-container card-deck">
<pagination-controls (pageChange)="p = $event"></pagination-controls>
</div>
All you should need is ngIf
<div class="section-container">
<table>
<div *ngFor="let system of systemList | paginate: { itemsPerPage: 190,
currentPage: p }; let i = index">
<app-system *ngIf="i % 2 == 0" [systemInput]="system"></app-system>
</div>
</table>
<div class="pagination section-container card-deck">
<pagination-controls (pageChange)="p = $event"></pagination-controls>
</div>
</div>
All we do is use a modulus expression to only show the even rows (The inverse would be i % 2 != 0)
app-system is a child element however, so there will be empty parent divs...

How to iterate over single character in a string

How can I iterate a string using the *ngFor?
I have a string with binary code (e.g. 0010) and dependendig on a single bit I have to show a different icon.
<div class="row" *ngFor="let item of subscribedCommandBus2Easy; let i = index">
<span class="numberCircleBus2Easy col-md-2">
{{item}}
</span>
<i *ngFor="let num of commandsDecimal">
<i ng-repeat="let el in num">
<span [ngClass]="el =='0' ? 'off-icon' : 'on-icon'">
//is this the way I access the single character?
</span>
</i>
</i>
</div>
I tried this code but it does not work.
commandsDecimal is my array of binary string. I want to loop commandsDecimal at index i (suppose the element is 1010) and if the character at position y is 0 I have to show an icon otherwise the other icon and so on...
Any suggestion?
The best way is to do a split on your string. With a custom pipe:
#Pipe({
name: 'split'
})
export class SplitPipe implements PipeTransform {
transform(value: any, args?: any): any {
return value.split('');
}
}
And then iterate over it. like that:
<div *ngFor="let item of myString">
<div *ngFor="let num of item | split item">
// access num
</div>
</div>
Example: https://stackblitz.com/edit/angular-8bkywr
In your component typescript
function getSplit(string) {
return string.split('').map(number)
}
In the template
*ngFor="let num of getSplit(commandsDecimal)"
You can do this without the need for any code in your component. Also ng-repeat is AngularJS syntax, not Angular 2+. In Angular 2+, ngFor is used to iterate in the HTML.
<ng-container *ngFor="let num of commandsDecimal">
<i *ngFor="let el of num.split('')" [ngClass]="el === '0' ? 'off-icon' : 'on-icon'"></i>
</ng-container>

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