Angular 8 => Condition : show an else value - javascript

I'm trying to filter a default value if no result is found on my query.
I tried using ng-template, but I simply don't manage to do it.
Rather than writing, here are images to better understand :
This is my successful filter : it correctly shows my datas once I filter them in the search box.
However if I try to enter an invalid data as done below :
It simply returns me an empty html table.
Which is not what I'd like to achieve. I'd like it instead to show a message saying : no data found.
How can I do it please?
And here is my source code :
Component :
import {Component, OnInit} from '#angular/core';
import {IProduct} from './product';
#Component({
selector: 'pm-products',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
///////////////////////////////////// PROPERTIES //////////////////////////////////////
// String Interpolation
pageTitle = 'Product List';
// Property binding
imageWidth = 50;
imageMargin = 2;
// Event binding
showImage = false;
// Two-way binding
// listFilter = 'cart';
// Filter products
private _listFilter: string;
// Filter Products Array
filteredProducts: IProduct[];
///////////////////////////////////// CONSTRUCTOR //////////////////////////////////////
constructor() {
this.filteredProducts = this.products;
this.listFilter = 'cart';
}
/////////////////////////////////// GETTERS/SETTERS ///////////////////////////////////
get listFilter(): string {
return this._listFilter;
}
set listFilter(value: string) {
this._listFilter = value;
/***
* If there is a list of filtered value, show the list of the filtered values => this.performFilter(this.listFilter)
* Else, (if there is no filtered) return the whole set of products
*/
this.filteredProducts = this.listFilter ? this.performFilter(this.listFilter) : this.products;
}
/////////////////////////////////////// METHODS ///////////////////////////////////////
// Get Products
products: IProduct[] = [
{
productId: 2,
productName: 'Garden Cart',
productCode: 'GDN-0023',
releaseDate: 'March 18, 2019',
description: '15 gallon capacity rolling garden cart',
price: 32.99,
starRating: 4.2,
imageUrl: 'assets/images/garden_cart.png'
},
{
productId: 5,
productName: 'Hammer',
productCode: 'TBX-0048',
releaseDate: 'May 21, 2019',
description: 'Curved claw steel hammer',
price: 8.9,
starRating: 4.8,
imageUrl: 'assets/images/hammer.png'
},
];
performFilter(filterBy: string): IProduct[] {
/**
* filterBy result => to lower case. => case insensitive comparison.
* Return a new array of the filtered productS by the product name,
* by checking if that product name given is an index of the an element in the product array.
*/
filterBy = filterBy.toLowerCase(); // 1.
return this.products.filter((product: IProduct) => product.productName.toLowerCase().indexOf(filterBy) !== - 1); // 2.
}
toggleImage = (): void => {
this.showImage = !this.showImage;
}
////////////////////////////////// LIFECYCLE HOOKS ///////////////////////////////////
ngOnInit(): void {
console.log('hello');
}
}
HTML
<div class="card">
<div class="card-header">{{pageTitle}}</div>
<!-- CARD -->
<div class="card-body">
<div class="row">
<div class="col-md-2"> Filter by:</div>
<div class="col-md-4">
<input [(ngModel)]="listFilter" type="text"/>
</div>
</div>
<div class="row">
<div class="col-md-6">
<h4>Filtered by: {{listFilter}} </h4>
</div>
</div>
<!-- ./CARD -->
<!-- TABLE -->
<div class="table-responsive">
<table *ngIf="products && products.length" class="table">
<thead>
<tr>
<th>
<button (click)="toggleImage()" class="btn btn-primary">{{showImage ? "Hide" : "Show"}} image</button>
</th>
<th>Product</th>
<th>Code</th>
<th>Available</th>
<th>Price</th>
<th>5 Star Rating</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of filteredProducts">
<td><img *ngIf="showImage" [src]="product.imageUrl" [title]="product.productName"
[style.width.px]="imageWidth" [style.margin.px]="imageMargin" alt=""></td>
<td>{{product.productName}}</td>
<td>{{product.productCode | lowercase | convertToSpaces: '-'}}</td>
<td>{{product.releaseDate}}</td>
<td>{{product.price | currency: 'EUR':'symbol':'2.2-2'}}</td>
<td>{{product.starRating}}</td>
</tr>
</tbody>
</table>
</div>
<!-- ./TABLE -->
</div>
</div>
Please take care of yourself.

Maybe just include an *ngIf="!filteredProducts.length" wherever you'd like to show your message.
ex.
<tr *ngFor="let product of filteredProducts">
//Your table stuff
</tr>
<tr *ngIf="!filteredProducts.length">
<td colspan="6">No data found</td>
</tr>

You have to add tr contains string no data found after ngfor tr
<tr *ngFor="let product of filteredProducts">
<td><img *ngIf="showImage" [src]="product.imageUrl" [title]="product.productName"
[style.width.px]="imageWidth" [style.margin.px]="imageMargin" alt=""></td>
<td>{{product.productName}}</td>
<td>{{product.productCode | lowercase | convertToSpaces: '-'}}</td>
<td>{{product.releaseDate}}</td>
<td>{{product.price | currency: 'EUR':'symbol':'2.2-2'}}</td>
<td>{{product.starRating}}</td>
</tr>
<tr *ngIf="filteredProducts.length === 0 " >
<td colspan="6" >Your message here </td>
</tr>

You can simply add a row that appears only when the list is empty and the search bar has any value. like this:
<tr *ngFor="let product of filteredProducts">...</tr>
<tr col-span ="6" *ngIf="!filteredProducts.length && listFilter.length"> uh oh,
could not find what you searched for</tr>

Related

In angular data is not showing in a table

hi am developing a rent calculator app in angular which take three inputs: rent, amount of rent increase per year; and number of years to calculate rent for. and give the result which is working fine. but i am having trouble to show that data in table as am new to angular i dont know the way to do that.
Html file
<div class="container">
<h1>Rent Calculator</h1>
<input type="number" placeholder="Enter your total rent" [(ngModel)]="rent" />
<input type="number" placeholder="Rent increase per Year" [(ngModel)]="increase">
<input type="number" placeholder="Number of Total Years" [(ngModel)]="years">
<button type="button" (click)="calculate()"> Calculate </button>
<br>
<table id="users">
<tr>
<th *ngFor="let column of headers">
{{column}}
</th>
</tr>
<tr *ngFor="let row of total">
<td *ngFor="let column of headers">
{{row[column]}}
</td>
</tr>
</table>
<!-- <h4 *ngFor="let r of total;let i=index">
Year {{i+1}} = {{r}} Rs
</h4> -->
//it works with above commented code
</div>
ts file
export class TableComponent implements OnInit {
headers: any = ['years', 'baseRent', 'New_rent'];
ngOnInit(): void {
}
constructor() { }
increase: any;
years: any;
rent: any;
total: any[] = []; //declare an array
calculate() {
// debugger;
this.total = [];
let previousRent = this.rent;
this.total.push(previousRent);
for (let i = 1; i < this.years; i++) {
const CurrentRent = previousRent * (1 + this.increase / 100);
previousRent = CurrentRent;
this.total.push(Math.round((CurrentRent + Number.EPSILON) * 100) / 100);
}
}
}
The first thing you need to learn is to avoid using any as your type. Angular uses TypeScript so we can leverage the type system.
I created 2 types: RentHeader and RentResult to represent the real world objects we need.
RentHeader will have a text for displaying on the table, and the key where we will use it to get the correct property from the RentResult.
RentResult have 3 properties which represent all the values you need: years, baseRent and newRent.
We use _rentResults array to store the object in the calculation function.
example.component.ts
//... import stuff here.
type RentHeader = {
readonly text: string;
readonly key: string;
};
type RentResult = {
readonly years: number;
readonly baseRent: number;
readonly newRent: number;
};
#Component({
...
})
export class ExampleComponent implements Oninit {
private _rentResults!: RentResult[];
public get rentResults() {
return this._rentResults;
}
public readonly headers: RentHeader[] = [
{text: 'Years', key: 'years'},
{text: 'Base Rent', key: 'baseRent'},
{text: 'New Rent', key: 'newRent'}
];
...
public calculate() {
// do your calculation here and replace the 0 with your calculation.
const result = {
years: 0,
baseRent: 0,
newRent: 0
};
this._rentResults.push(result);
}
}
example.component.html
...
<table>
<tr>
<th *ngFor="let header of headers">
{{header.text}}
</th>
</tr>
<tr *ngFor="let row of rentResults">
<td *ngFor="let header of headers">
{{row[header.key]}}
</td>
</tr>
</table>
...

Add a row from one table to another in LWC

I am very new to LWC and Javascript. I have an LWC component with a search bar and a table (created in HTML file) showing the search result. I want to add another column in the table with a button on each row which allows me to add that whole row into another table in other component.
This is my HTML file.
' <template>
<lightning-card title="Account Search Bar" icon-name="standard:account">
<lightning-input type="search" onchange={handleKeyChange} class="slds-m-bottom_small" label="Search"
value={accountName}>
</lightning-input>
<table id="table">
<thead>
<tr>
<th class="" scope="col">
<div class="slds-truncate" title="Account Name">Name</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Account Phone">Phone</div>
</th>
<th class="" scope="col">
<div class="slds-truncate" title="Select Account">Select Account</div>
</th>
</tr>
</thead>
<tbody>
<template for:each={accountList} for:item="accountObj" for:index="index">
<tr class="table" key={accountObj.Id}>
<td class="name">{accountObj.Name}</td>
<td class="phone">{accountObj.Phone}</td>
<td class="addButton"><input type="submit" value="Add" onclick={addField}></td>
</tr>
</template>
</tbody>
</table>
</lightning-card>
'
I have added the buttons nd I am trying to get the data of the row from which the button is clicked. But I am getting 'undefined' in every object.
This is my js file.
import { LightningElement,wire,track } from 'lwc';
import getAccounts from '#salesforce/apex/AccountSearchCls.getAccounts';
const DELAY = 300;
export default class TestComponentSearch extends LightningElement {
accountName='';
#track accountList =[];
#track buttonId=1;
#wire(getAccounts,{actName:'$accountName'})
retrieveAccouts({error,data}){
if(data){
this.accountList = data;
}
else if(error){
}
}
handleKeyChange(event){
const searchString= event.target.value;
window.clearTimeout(this.delayTimeout);
this.delayTimeout = setTimeout(()=>{
this.accountName =searchString;
},DELAY);
}
addField()
{
console.log("In addField");
var table = document.getElementById('table');
selected = table.getElementsByClassName('tbody')[0];
var rows= selected.getelEmentsByTagName('tr');
}
}
I am not sure what is the problem or if this is the right way to do it. I would appreciate if anyone helps me in it.
Add an event parameter to addField method get the selector from which the button was clicked and try to find the parent row using the table class that you have defined already and process the columns using innerText.
addField(event)
{
let rows=event.target.closest("tr.table");
let cols=rows.querySelectorAll('td');
let name=cols[0].innerText;
let phone=cols[1].innerText;
alert('Name :'+name+' Phone :'+phone);
}
I am not sure about your complete use case. But, the use case you mentioned is achievable through lightning base components and no need to use table tags.
HTML markup
<lightning-input type="search" value={searchStr} onchange={searchChangeHandler}>
</lightning-input>
<lightning-datatable columns={columns} data={data} key-field="Id"
onrowaction={handleRowAction}>
</lightning-datatable>
</template>
Javascript code
// MyScratchComp.js
import { LightningElement, wire } from 'lwc';
import getAccounts from '#salesforce/apex/LWCHelper.getRecords';
export default class MyScratchComp extends LightningElement {
data;
connectedCallback() {
this.initDefaultValues();
this.getData();
}
initDefaultValues() {
this.searchStr = 'Test';
this.columns = [{
label : 'Name',
fieldName : 'Name',
type : 'Text'
},{
label : 'Industry',
fieldName : 'Industry',
type : 'Text'
},{
label : 'PhotoURL',
fieldName : 'PhotoUrl',
type : 'Text'
},{
label : 'Action',
type : 'button',
typeAttributes : {
label : 'click here'
}
}];
}
searchChangeHandler(event) {
this.searchStr = event.target.value;
this.getData();
}
async getData() {
try {
this.data = await getAccounts(
{
searchQuery : `Select Id, Name, Industry, PhotoURL From Account Where name like '%${this.searchStr}%' LIMIT 10`
}
);
}
catch(e) {
console.error(JSON.stringify(e));
}
}
handleRowAction(event) {
alert('Row selected - ' + JSON.stringify(event.detail.row));
}
}
Apex code
/* LWCHelper Apex class */
public with sharing class LWCHelper {
#AuraEnabled
public static List<SObject> getRecords(String searchQuery){
try {
return Database.query(searchQuery);
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
}

How to use subscribe in Angular with an array of inputs and how to sum their values?

I have a reactive angular form. The form is a simple scoreboard. I would like to be able to add the rounds up together to make up a score. So when a round is finished and the score for that round is entered, it will sum up all the total rounds into that players score.
This is what I have so far:
form.component.html
<section [formGroup]="board">
<table class="table table-bordered" formArrayName="scoreboard">
<tr>
<th colspan="2">Scoreboard:</th>
<th width="150px">
<button type="button" (click)="addPlayer()" class="btn btn-primary">
Add Additional Players
</button>
</th>
</tr>
<tr
*ngFor="let quantity of scoreboard().controls; let i = index"
[formGroupName]="i"
>
<td>
Name :
<input type="text" formControlName="name" class="form-control" />
</td>
<td>
Round1
<input type="text" formControlName="round1" />
Round2
<input type="text" formControlName="round2" />
</td>
<td>
Score:
<input type="text" formControlName="score" class="form-control" />
</td>
</tr>
</table>
{{ this.board.value | json }}
</section>
form.component.ts
import { Component } from '#angular/core';
import { FormBuilder, FormGroup, FormArray } from '#angular/forms';
#Component({
selector: 'app-basic-form',
templateUrl: './basic-form.component.html',
styleUrls: ['./basic-form.component.css']
})
export class BasicFormComponent {
board: any;
constructor (private fb: FormBuilder) {
this.board = this.fb.group({
scoreboard: this.fb.array([this.game(), this.game()])
});
}
scoreboard() : FormArray {
return this.board.get("scoreboard") as FormArray
}
game(): FormGroup {
return this.fb.group({
name: '',
score: '',
round1: [''],
round2: ['']
})
}
addPlayer() {
this.scoreboard().push(this.game());
}
onSubmitForm () {
console.log(this.board.value);
}
}
Really just starting to learn Angular and wanted to try something on my own. If you could be detailed or show me somewhere I can learn additional information about the solution that would be great!
You can listen to your group's controls value changes, and update the score accordingly. Your form array is made of list of form groups (the return value of game function), these groups have controls which holds the values for their respective index, so when you create a form group, you can listen to round1 and round2 changes, and update score accordingly. For example, what I did below is using combineLatest function to merge both value changes observables (round1 + round2) and then update the score accordingly. So now every new form group pushed to your form array will have its own value change listener and will update the score.
game(): FormGroup {
const gameGroup = this.fb.group({
name: '',
score: '',
round1: [''],
round2: ['']
});
combineLatest(gameGroup.get('round1').valueChanges,gameGroup.get('round2').valueChanges)
.subscribe(([r1,r2]) => gameGroup.get('score').setValue(Number(r1) + Number(r2)));
return gameGroup;
}

Issue with Aurelia class.bind with checked.bind

I am trying to use class.bind in a way that makes it dependent on checked.bind.
My use case is pretty simple. I have a list of items, displayed using a table. Each row of this table has a checkbox. I want to mark a row as "selected" whenever the corresponding checkbox (of the row) is checked.
For this I have used following binding:
<table class="table table-striped table-hover table-responsive">
<tbody>
<tr repeat.for="item of items" class.bind="$parent.selectedItems.indexOf(item)>-1?'info':''">
<td>
<input type="checkbox" checked.bind="$parent.selectedItems" model.bind="item" />
</td>
<td>${item.id}</td>
<td>${item.name}</td>
</tr>
</tbody>
</table>
However, the same doesn't work as intended, and this can be seen in this plunk.
As a workaround I used a getter with #computedFrom('selectedItems', 'items') and/or declarePropertyDependencies(App, 'classes', ['selectedItems', 'items']); , as follows:
import {computedFrom, declarePropertyDependencies} from "aurelia-framework";
export class App {
...
#computedFrom('selectedItems', 'items')
get classes() {
const self = this;
const retval= self.items.map((item: any) => {
return self.selectedItems.indexOf(item) > -1 ? "info" : "";
});
return retval;
}
}
//declarePropertyDependencies(App, 'classes', ['selectedItems', 'items']);
However, this too does not work as can be seen here in this workaround plunk.
It only works, if none of #computedFrom and/or declarePropertyDependencies is used, and that obviously involves dirty-checking.
Is there a clean way to do this?
The binding system will reevaluate the class binding expression class.bind="$parent.selectedItems.indexOf(item)>-1?'info':''" anytime a property used in the expression changes. The selectedItems property never changes, it remains the same array instance. Understandably this is a little confusing because the array instance is mutating. Here's a workaround you can use: add selectedItems.length to the binding expression... we know it will change when items are pushed/popped from the array.
Here's an example: https://gist.run?id=09d32941842352ff0025
app.html
<template>
<p>${message}</p>
<table class="table table-striped table-hover table-responsive">
<tbody>
<tr repeat.for="item of items" class.bind="selectedItems.length === 0 || selectedItems.indexOf(item) === -1 ? '' : 'info'">
<td>
<input type="checkbox" checked.bind="selectedItems" model.bind="item" />
</td>
<td>${item.id}</td>
<td>${item.name}</td>
</tr>
</tbody>
</table>
${selectedItems.length} items selected
</template>
app.js
export class App {
constructor(router) {
this.message = "Hello World!";
this.items = [{
id: 1,
name: "A"
}, {
id: 2,
name: "B"
}, {
id: 3,
name: "C"
}];
this.selectedItems=[];
}
}

Angular 2 filtering

I've tried to filter in Angular 2 app in version alpha 22. I've tried many ways how to do it but nothing works...
<table class="tabulka">
<tr>
<th>ID</th><th>Typ</th><th>Priorita</th><th>Aplikace</th><th>Souhrn</th><th>Hlásil</th><th>Stav</th><th>Termín</th><th>Akce</th>
</tr>
<tr *for="#x of datas">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td *if="x.Priority == 1" ><img src="./img/red.png"></td>
<td *if="x.Priority == 0"></td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td>
</tr>
</table>
Please help! How do you do filtering in angular 2 using typescript?
In angular 1.4.x it works this way:
<table class="tabulka">
<tr ng-repeat="x in datas| filter:searchText|filter:{Aplication:search}| filter:{Person:podle}">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td>{{x.Priority}}</td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td>
</tr>
</table>
In angular 2.0.0-beta.0, you'll need to implement a pipe that transform the array depending on your application needs,
#Pipe({
name: 'search'
})
export class SearchTextPipe implements PipeTransform {
transform(value: any[] , args: any[]) {
const searchText = args[0];
const field = args[1];
if (!searchText) {
return value;
}
return value.filter ((item) => {
if (field) {
return item[field].includes(searchText);
}
return _(item)
.values()
.includes( searchText );
})
}
}
Then you can use it in other components:
#Component({
...
pipes: [SearchTextPipe]
})
and in the template:
*ngFor="#item of list | search:searchInput:field"
Go to the console and type
ng generate pipe filter
Then go edit the newly created file (src/app/filter.pipe.ts) and replace
transform(value: any, args?: any): any {
return null;
}
by
transform(value: any, args?: any): any {
if (!value || !args) return value;
if (typeof args == "string"){
return value.filter(item => item.toLowerCase().indexOf(args.toLowerCase()) !== -1);
} else {
let key = Object.keys(args)[0];
return value.filter(item => item[key].toLowerCase().indexOf(args[key].toLowerCase()) !== -1);
}
}
Usage
Now, you can use your filter as follow
// app.component.ts
list = ["Hello", "Hi and hello", "Bonjour"];
list_of_objects = [
{ id: 0, content: "Hello" },
{ id: 1, content: "Hi and hello" },
{ id: 2, content: "Bonjour" }
];
// app.component.html
<p>Simple array</p>
<ul>
<li *ngFor="let item of list | filter:'hello' ">{{ item }}</li>
</ul>
<p>Array of JSONs</p>
<ul>
<li *ngFor="let item of list_of_objects | filter:{ content:'hello' } ">{{ item.title }}</li>
</ul>
And all of that will display :
Simple array
Hello
Hi and hello
Array of JSONs
Hello
Hi and hello
You have to write *ng-for="#x of datas" and *ng-if="x.Priority == 1""
<table class="tabulka"> <tr>
<th>ID</th><th>Typ</th><th>Priorita</th><th>Aplikace</th><th>Souhrn</th><th>Hlásil</th><th>Stav</th><th>Termín</th><th>Akce</th> </tr> <tr *ng-for="#x of datas">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td *ng-if="x.Priority == 1" ><img src="./img/red.png"></td>
<td *ng-if="x.Priority == 0"></td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td> </tr>
I played around with the code below. i was looking for the same search function. But for now this fits my needs. Still want to find a way the make the pipe dynamic. Maybe you find the solutions for this one.
import {Pipe} from 'angular2/core';
#Pipe({
name: 'filterByDone',
pure: false,
})
export class SearchPipe {
transform (value, [queryString]) {
if (value==null) {
return null;
}
return value.filter((todo)=> todo.done !== '1')
}
}
Basicly, add a function to trigger search box textvalue changed. Inside the function, create a for loop to add matched data.
TextChanged(searchText){
var filteredList = [];
For(let item of this.oldList){
If(item.contains(seaechText))
FilteredList.push(item);
}
This.oldList = filteredList;
}
}

Categories