I'm doing an app in Angular 6, where I'm getting data from JSON API to table. I have to sort this data's after click on header.
I don't know, how should I do it. I am a beginner at Angular, I am asking for your understanding
My .ts code
export class AppComponent implements OnInit {
readonly ROOT_URL = '...';
datas: Observable<Data[]>;
constructor(private http: HttpClient) {
}
sortTable(parm) {
this.datas.subscribe(item => item.sort((a: any, b: any) => {
return a[parm] - b[parm];
}));
}
getDatas() {
this.datas = this.http.get<Data[]>(this.ROOT_URL);
}
ngOnInit() {
this.getDatas();
}
}
Interface
export interface Data {
long: string;
perc: number;
price: number;
}
And HTML Code
<div class="col-md-6">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col" (click)="sortTable(long)">Nazwa</th>
<th scope="col">Cena</th>
<th scope="col">Zmiana(24h)</th>
</tr>
</thead>
<tbody *ngFor="let data of datas | async" style="font-size: 12px;">
<tr>
<td>{{ data.long }}</td>
<td>{{ data.price }} $</td>
<td>{{ data.perc }} %</td>
</tr>
</tbody>
</table>
</div>
You can remove async pipe, and subscribe to data with subscription function. So use sort function of Array.
...
datas: Array<Data>;
...
sortTable(parm) {
// you can use one of this solutions, but I recomend localeCompare
// this.datas.sort((a, b)=>a[parm] > b[parm]);
this.datas.sort((a, b)=> a[parm].localeCompare(b[parm]) );
}
getDatas() {
this.http.get<Data[]>(this.ROOT_URL).subscribe(it =>this.datas = it)
}
<tbody *ngFor="let data of datas" style="font-size: 12px;">
You can achieve ascending and descending sort using as below
sortTable(param){
this.filteredData.sort((a, b)=> {
return -1;
});
}
Related
I'm new to OOP and angular.
currently, I want to use reusable table with pagination that makes a request API if page change (pagination inside table component).
the problem is when I access my method using callback from table component (Child) I got undefined.
but when I try to move pagination to MasterGudang (Parent) Components it's work.
I don't really understand what's going on.
Error undefined
but here some code.
table.component.ts
import { Subject } from 'rxjs';
#Component({
selector: 'ngx-table-custom',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss']
})
export class TableComponent implements OnInit {
constructor() { }
#Input() items: any;
#Input() callback: any;
#Input() columns: [];
p: number = 1;
#ContentChild('action', { static: false }) actionRef: TemplateRef<any>;
ngOnInit(): void {
this.items = new Subject();
this.items.next();
}
onChangePage = (evt) => {
this.callback()
}
Gudang.component.ts
import { MasterGudangService } from '../../../../#core/services/master-service/menu-gudang/gudang/masterGudang.service';
#Component({
selector: "ngx-gudang",
templateUrl: './gudang.component.html',
styleUrls: ['./gudang.component.scss'],
})
#Injectable({
providedIn: 'root'
})
export class GudangComponent implements OnInit {
constructor(
public masterGudangService: MasterGudangService
) {
console.log(masterGudangService)
}
tableData: [];
isEdit: boolean = false;
currentPage: number = 1;
ngOnInit(): void {
this.getList();
}
getList (page?: number) {
this.masterGudangService.getPgb(page? page: this.currentPage).subscribe(response => {
const { data: { content, totalElements, size, number } } = response;
this.tableData = Object.assign({
data: content,
total: totalElements,
size: size,
number: number
});
});
}
}
And here I passing my function which is getList to table component
gudang.component.html
<ngx-table-custom [callback]="getList" [columns]="column" [items]="tableData">
<ng-template let-item #action>
<div class="row">
<button nbButton status="success" (click)="open(dialog, item, true)" class="mx-2" size="tiny"><nb-icon icon="edit"></nb-icon></button>
<button nbButton status="danger" (click)="onDelete(item)" size="tiny"><nb-icon icon="trash"></nb-icon></button>
</div>
</ng-template>
</ngx-table-custom>
MasterGudangService.ts
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class MasterGudangService {
constructor(private http: HttpClient) { }
getPgb (page: number = 1, perPage: number = 10) :any {
return this.http.get(`my-api-url/pgb?page=${page}&size=${perPage}`)
}
}
table.component.html
<div class="row">
<div class="col-12">
<table class="table table-md table-striped">
<thead>
<tr style="background-color: #3366ff; color: #fff;">
<th *ngFor="let column of columns" class="text-basic">{{ column.value }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of items.data | paginate: { itemsPerPage: 10, currentPage: p, totalItems: items.total }; index as idx;">
<td *ngFor="let column of columns">
<div *ngIf="column.key === 'number';"><b class="text-basic">{{ idx + 1 }}</b></div>
<div *ngIf="column.key !== 'action' && !isNested(column.key);" class="text-basic">{{ item[column.key] }}</div>
<div *ngIf="isNested(column.key);" class="text-basic">{{ getKeys(item, column.key) }}</div>
<!-- <div *ngIf="column.key === 'action; action_container"></div> -->
<ng-template [ngIf]="column.key === 'action'" #action_content>
<ng-container
*ngIf="actionRef"
[ngTemplateOutlet]="actionRef"
[ngTemplateOutletContext]="{$implicit:item}">
</ng-container>
</ng-template>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-12" align="center">
<pagination-controls (pageChange)="onChangePage($event)"></pagination-controls>
</div>
</div>
The context of Gudang.component.ts will not be available using callback from table component.
The proper way to implement should be passing the event instead of passing function for callback
table.component.ts
#Output() pageChange = new EventEmitter()
onChangePage = (evt) => {
this.pageChange.emit(evt);
}
gudang.component.html
<ngx-table-custom (pageChange)="getList($event)" [columns]="column" [items]="tableData">
...
</ngx-table-custom>
based on the error, it seems like masterGudangService is null at the time you are trying to access it. Adding this code might help you eliminate the error and at least debug what is going on and get a step further.
ngOnInit(): void {
if(this.masterGudangService)
this.getList();
else
console.log('service not defined!');
}
You could define a helper Method in GudangComponent
getListCallback() {
return this.getList.bind(this);
}
and use it here
<ngx-table-custom [callback]="getListCallback()" [columns]="column" [items]="tableData">
This question already has answers here:
JQuery - $ is not defined
(36 answers)
Closed 4 years ago.
I'm trying to add Paging and sorting to my table but I got this error , howerver I follow all the steps which listed here
http://l-lin.github.io/angular-datatables/#/getting-started.
I already check the previous problem but I did't work with me
I install all its dependencies
Here's the code of the component :-
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ProductService } from '../../service/product-service.service';
import { Subscription, Subject } from 'rxjs';
#Component({
selector: 'app-admin-products',
templateUrl: './admin-products.component.html',
styleUrls: ['./admin-products.component.css']
})
export class AdminProductsComponent implements OnInit, OnDestroy {
products: any[];
filteredProducts: any[];
subscribtion: Subscription;
dtOptions: DataTables.Settings = {};
dtTrigger: Subject<any> = new Subject();
constructor(private productService: ProductService) {
this.subscribtion = productService.getAll().
// We take a copy of Products and Assigned to filteredProducts
subscribe(
products => {
this.filteredProducts = this.products = products;
this.dtTrigger.next();
}
);
}
ngOnInit() {
this.dtOptions = {
pagingType: 'full_numbers',
pageLength: 5,
processing: true
};
}
filter(queryStr: string) {
// console.log(this.filteredProducts);
if (queryStr) {
this.filteredProducts = this.products.
filter(p => p.payload.val().title.toLowerCase().includes(queryStr.toLowerCase()));
} else {
this.filteredProducts = this.products;
}
}
ngOnDestroy(): void {
// to UnSubscribe
this.subscribtion.unsubscribe();
}
}
Here's the code of the the HTML :-
I follow also all the steps here
<p>
<a routerLink="/admin/products/new" class="btn btn-primary">New Product</a>
</p>
<p>
<input type="text"
#query
(keyup)="filter(query.value)"
placeholder="Search ..." class="form-control">
</p>
<table
datatable [dtOptions]="dtOptions"
[dtTrigger]="dtTrigger" class="table" >
<thead class="thead-dark">
<tr>
<th scope="col">Title</th>
<th scope="col">Price</th>
<th scope="col">Edit</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of filteredProducts">
<td>{{ product.payload.val().title }}</td>
<td>{{ product.payload.val().price }}</td>
<td>
<a [routerLink]="['/admin/products/', product.key]">Edit</a>
</td>
</tr>
</tbody>
</table>
$ not defined mostly means you are not including JQuery.
try adding: to your program
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" type="text/javascript"></script>
<script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script>
source
I am creating a simple filter pipe in my app, here is what I have.
filter component:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'dataFilter'
})
export class DataFilterPipe implements PipeTransform {
transform(value: any[], input: string) {
if (input) {
input = input.toLowerCase();
return value.filter(function (el: any[]) {
return el.toLowerCase().indexOf(input) > -1;
});
}
return value;
}
}
here is componet.html
<div class="card movies-list" id="movies-list">
<div class="card-header movies-list_header">
<h1>Content: <small>Best movies of 2010's</small></h1>
</div>
<div class="card-body">
<div class="row movies-list_filter">
<div class="col-md-2">Filter By</div>
<div class="col-md-4">
<input type='text' [(ngModel)]="listFilter">
</div>
</div>
<div class="row">
<div class="col-md-6">
<h1>filterdbY: {{listFilter}}</h1>
</div>
</div>
<table class="table table-striped table-responsive">
<thead>
<tr class="movies-list_tbheader">
<td></td>
<th>Title</th>
<th>Realease</th>
<th>Rating</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="movies-list_data" *ngFor="let movie of movies | dataFilter:listFilter">
<td>
<img *ngIf="movie.poster_path" class="thumbnail" src="http://image.tmdb.org/t/p/w500/{{movie.poster_path}}">
</td>
<td>{{ movie.title }}</td>
<td>{{ movie.release_date }}</td>
<td>{{movie.vote_average}}</td>
<td>{{movie.overview}}</td>
</tr>
</tbody>
</table>
</div>
</div>
and in component.ts I have declared the variable listFilter like this:
export class MoviesComponent implements OnInit {
listFilter = '';
-----------
}
when I run my app and try to seach some text I get the following error
ERROR TypeError: el.toLowerCase is not a function
what is wrong with my code? any help will be apreciated
It is not considered "best practice" to use a pipe to filter data. Rather, do it in your component. For example:
// Local filter
performFilter(filterBy: string): IMovie[] {
if (filterBy) {
filterBy = filterBy.toLocaleLowerCase();
return this.movies.filter((movie: IMovie) =>
movie.title.toLocaleLowerCase().indexOf(filterBy) !== -1);
} else {
return this.movies;
}
}
I have a detailed blog post about this here: https://blogs.msmvps.com/deborahk/filtering-in-angular/
It details why a pipe should not be used to filter and several different techniques for performing the above filter operation.
BTW ... this code really looks familiar. :-) This is the code from one of my OLD Angular v2 talks ... before we were notified by the team that using pipes in this way was not recommended.
Oh ... and I looked at the link you provided as a comment in the other answer (Which has since been deleted) and the code is NOT the same as you posted above. Here is the "working" code from your link:
transform(value: IProduct[], filterBy: string): IProduct[] {
filterBy = filterBy ? filterBy.toLocaleLowerCase() : null;
return filterBy ? value.filter((product: IProduct) =>
product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1) : value;
}
Here is the code from above:
transform(value: any[], input: string) {
if (input) {
input = input.toLowerCase();
return value.filter(function (el: any[]) { // <---- data type!
return el.toLowerCase().indexOf(input) > -1;
});
}
return value;
}
Notice the data type passed to the filter function is any[]. It should just be any.
Using Angular 5, AngularFire2, and Firestore, I have a set of items that I want listed out in a grid that will be editable from the grid itself.
In the template, the grid is iterating over a FormArray inside a FormGroup.
<form [formGroup]="form">
<table class="table">
<thead class="thead-dark">
<tr>
<th scope="col">Serial Number</th>
<th></th>
</tr>
</thead>
<tbody formArrayName="devices">
<tr *ngFor="let device of form.controls.devices.controls; let i = index" [formGroupName]="i">
<th scope="row"><input type="text" class="form-control" formControlName="serialNumber"></th>
<td><button type="button" class="btn-sm btn-danger" (click)="deleteDevice(i)">Delete</button></td>
</tr>
</tbody>
</table>
</form>
In my Angular component, I'm calling buildForm() inside ngOnInit() to initially generate the form to give the template something to work with. If I don't do this, I get errors in the console.
I'm then generating the form again in the collection's snapshotChanges() in order to make sure the data is synced properly between Firebase and the FormArray.
If I don't do this, I'll get weird behavior such as rows remaining in the grid even after the item has been deleted or extra rows appearing once I add in another item.
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
devicesCol: any;
devices: any;
serialNumber: string;
form: any;
constructor(private afs: AngularFirestore, private fb: FormBuilder) { }
ngOnInit() {
this.buildForm(); // Generate the form initially to give the template something to work with
this.devicesCol = this.afs.collection('devices', ref => ref.orderBy('deviceName'));
this.devicesCol.snapshotChanges().map(actions => {
return actions.map(a => {
const id = a.payload.doc.id;
const data = a.payload.doc.data();
return { id, data };
});
}).subscribe(result => {
this.form = this.fb.group({
devices: this.fb.array([]);
});
var devicesForm = this.form.get('devices');
this.devices.forEach((element) => {
devicesForm.push(this.fb.group({ 'id': element.id, 'serialNumber': element.data.serialNumber, 'deviceName': element.data.deviceName }));
}
});
}
buildForm() {
this.form = this.fb.group({
devices: this.fb.array([]);
})
}
addDevice() {
this.afs.collection('devices').add({ 'serialNumber': this.serialNumber, 'deviceName': '' }); // Add to firebase
}
deleteDevice(id) {
var devices = this.form.get('devices');
var deviceID = devices.at(id).get('id').value;
this.afs.doc('devices/' + deviceID).delete(); // Remove from firebase
devices.removeAt(id); // Remove from FormArray (probably unnecessary since the form will be rebuilt in snapshotChanges()
}
}
While this does work, I'm not sure it's best way to go about it. Is there a better way to sync data between Firebase and a FormArray?
I'm building my first Angular app and I'm trying to integrate firestore.
So far I was able to retrieve data from firestore and also log the id with snapshot but I'm not being able to bring it all together.
Is there a way to add the id to the client Model?
I was reading the angularfire2 documentation where it says that we can't use the $key property and now we should use snapshot but I can't figure it out.
Thanks
This is my Service
#Injectable()
export class ClientService {
clientsCollection: AngularFirestoreCollection<Client>;
clients: Observable<Client[]>;
snapshot: any;
constructor(private afs: AngularFirestore) {
this.clientsCollection = this.afs.collection('clients');
this.clients = this.clientsCollection.valueChanges();
// snapshot for id/metadata
this.snapshot = this.clientsCollection.snapshotChanges()
.map(arr => {
console.log(arr);
});
}
}
My client.component.ts
#Component({
selector: 'app-clients',
templateUrl: './clients.component.html',
styleUrls: ['./clients.component.css']
})
export class ClientsComponent implements OnInit {
clients: Client[];
snapshots: any[];
constructor(
public clientService: ClientService
){}
ngOnInit(){
this.clientService.clients.subscribe(clients => {
this.clients = clients;
});
}
}
And my client.component.html
<table *ngIf="clients?.length > 0; else noClients" class="table table-striped">
<thead class="thead-inverse">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Balance</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let client of clients">
<td></td>
<td>{{ client.firstName }} {{ client.lastName }}</td>
<td>{{ client.email }}</td>
<td>{{ client.balance }}</td>
<td>Details</td>
</tr>
</tbody>
</table>
<ng-template #noClients>
<hr>
<h5>There are no clients in the system</h5>
</ng-template>
this is what I have so far
You can find answer in angularfire2 documents
export class AppComponent {
private shirtCollection: AngularFirestoreCollection<Shirt>;
shirts: Observable<ShirtId[]>;
constructor(private readonly afs: AngularFirestore) {
this.shirtCollection = afs.collection<Shirt>('shirts');
// .snapshotChanges() returns a DocumentChangeAction[], which contains
// a lot of information about "what happened" with each change. If you want to
// get the data and the id use the map operator.
this.shirts = this.shirtCollection.snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data() as Shirt;
const id = a.payload.doc.id;
return { id, ...data };
});
});
}
}
and this template
<ul>
<li *ngFor="let shirt of shirts | async">
{{ shirt.id }} is {{ shirt.price }}
</li>
</ul>