Ionic 4 read Local JSON file with Search Bar - json

Currently I've been facing issues with being able to do filtering on local JSON data in my Ionic project. I've looked at other resources and used that as reference when working on writing the filterItems function for the application. I haven't been receiving any errors in the console when using the function, however the table is not filtering or returning the filtered items to the ngx-datatable that I am using to display the data to the application. I am wondering if this problem has to do with either the filterItems function, or the way I am loading the data to the application from the local json file. Any assistance would be of great help. I'll be including both the HTML code and the typescript code.
HTML code
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start" class="button_style">
<ion-button (click)="switchStyle()">
{{ tablestyle }}
</ion-button>
</ion-buttons>
<ion-searchbar animated slot="end" (ionInput)="filterItems($event)" placeholder="Search by Path Request"></ion-searchbar>
</ion-toolbar>
</ion-header>
<ion-content>
<ngx-datatable class="request_table"
[ngClass]="tablestyle"
[rows]="items"
[columnMode]="'force'"
[headerHeight]="50"
[rowHeight]="'auto'">
<ngx-datatable-column name="requestid"></ngx-datatable-column>
<ngx-datatable-column name="number"></ngx-datatable-column>
<ngx-datatable-column name="requeststatus"></ngx-datatable-column>
<ngx-datatable-column name="animalcount"></ngx-datatable-column>
<ngx-datatable-column name="primaryinvestigator"></ngx-datatable-column>
<ngx-datatable-column name="studypathologist"></ngx-datatable-column>
<ngx-datatable-column name="Actions" sortable="false" prop="name">
<ng-template let-row="row" let-value="value" ngx-datatable-cell-template>
<ion-button size="small" fill="outline" (click)="goToProcedureDetails(row)">View</ion-button>
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
</ion-content>
Typescript code
import { AlertController } from '#ionic/angular';
import { HttpClient } from '#angular/common/http';
import { Router, ActivatedRoute } from '#angular/router';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
#Component({
selector: 'app-request',
templateUrl: './request.page.html',
styleUrls: ['./request.page.scss'],
})
export class RequestPage implements OnInit {
items: any[];
searchItems: any;
public RequestFilter: string[];
tablestyle = 'bootstrap';
constructor(private alertCtrl: AlertController, private http: HttpClient, private router: Router) { }
ngOnInit() {
this.loadData();
}
loadData(){
let data:Observable<any>;
data = this.http.get('assets/requests.json');
data.subscribe(data => {
this.items = data;
console.log(this.items);
});
this.initializeItems();
}
initializeItems(){
this.RequestFilter = this.items;
}
goToProcedureDetails(row){
this.router.navigate(['/view-procedure', row.pathrequestid]);
console.log(row.pathrequestid);
}
filterItems(ev:any){
// Reset items back to all of the items
this.initializeItems();
// set val to the value of the searchbar
var val = ev.target.value;
// if the value is an empty string don't filter the items
if (val && val.trim() != '') {
this.RequestFilter = this.items.filter((item) => {
return (item.toString().toLowerCase().indexOf(val.toString().toLowerCase()) > -1);
})
}
}
}

You code can't work because you are filtering on object
items: any[];
return (item.toString().toLowerCase().indexOf(...
If you want to filter on one column (for example the requeststatus column), you can do that :
return (item.requeststatus.toString().toLowerCase().indexOf(val.toString().toLowerCase()) > -1);
If you want to filter on all columns of the table, I suggest you to take a look at this question

Related

Ionic 4 Filter JSON by multiple values

Currently I've been facing an issue when it comes to filtering a local JSON file by multiple criteria. I originally thought this would be a simple fix where you could just club multiple conditions using and(&&). However, whenever the data is loaded to the Ngx-Datatable, nothing is appearing. The filtering has been working with a single condition, which is why I find it really odd that multiple criteria is not working. Could it possibly be an issue with the JSON file? Do I have to use another method to do this? Or is it the way I'm loading the data view? I'm really curious as to why this isn't working as I figured that .filter() could handle the multiple criteria as it has been working with the single condition provided before.
TypeScript File
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import data from './../../assets/pathrequests.json';
import { NavController } from '#ionic/angular';
import { NavigationExtras } from '#angular/router';
import { AlertController } from '#ionic/angular';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
#Component({
selector: 'app-view-necropsy-details',
templateUrl: './view-necropsy-details.page.html',
styleUrls: ['./view-necropsy-details.page.scss'],
})
export class ViewNecropsyDetailsPage implements OnInit {
pathrequestid: string;
procedureid: string;
requestdate: string;
animalqty: string;
marker: string;
method: string;
fixative: string;
handling: string;
processing: string;
items: any[];
private pathrequests: any[] = data;
tablestyle = 'bootstrap';
constructor(private alertCtrl: AlertController, private http: HttpClient, private route: ActivatedRoute, private router: Router, public navCtrl: NavController) { }
ngOnInit() {
this.route.queryParams.subscribe(params => {
this.pathrequestid = params["pathrequestid"];
this.procedureid = params["procedureid"];
this.requestdate = params["requestdate"];
this.animalqty = params["animalqty"];
this.marker = params["marker"];
this.method = params["method"];
this.fixative = params["fixative"];
this.handling = params["handling"];
this.processing = params["processing"];
});
this.loadData();
}
loadData(){
let data:Observable<any>;
data = this.http.get('assets/tissue.json');
data.subscribe(data => {
this.items = data.filter(item => item.pathrequestid === this.pathrequestid && item.procedureid === this.procedureid);
console.log(this.items);
})
}
}
HTML Template
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-menu-button menu ="main-menu"></ion-menu-button>
</ion-buttons>
<ion-buttons>
<ion-back-button defaultHref="/view-procedure"></ion-back-button>
</ion-buttons>
<ion-buttons class="button_style" slot="end">
<ion-button (click)="switchStyle()">
{{ tablestyle }}
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ngx-datatable class="necropsydetails_table"
[ngClass]="tablestyle"
[rows]="items"
[columnMode]="'force'"
[headerHeight]="60"
[rowHeight]="'auto'">
<ngx-datatable-column name="tissue"></ngx-datatable-column>
<ngx-datatable-column name="collectflg"></ngx-datatable-column>
<ngx-datatable-column name="weighflg"></ngx-datatable-column>
<ngx-datatable-column name="photoflg"></ngx-datatable-column>
<ngx-datatable-column name="tissuecomment"></ngx-datatable-column>
</ngx-datatable>
</ion-content>
We may rule out the filter as it works. Please post the template as well to aid other viewers.
Given the info available, it may be with the model or template; sometimes needing poking the framework to forcibly render (i.e. Change detection in Angular). I could suggest:
Delaying showing the results view until items is assigned.
Moving the filter inside a pipe and use the async pipe to display the already-filtered data. Items would need to be an Observable though.
this.http.get('assets/tissue.json')
.pipe(filter(item => /*criteria here*/)
then on the template
*ngFor="let item of items | async"
Hope these help.
Your filter should work, but you may have a race condition, or bad parameters. this.procedureid is set in the queryParams async call, but load data is called synchronously but also calls an async function (the http get).
If you move the call to this.loadData() inside the subscribe, that should make sure the data.
ngOnInit() {
this.route.queryParams.subscribe(params => {
this.pathrequestid = params["pathrequestid"];
this.procedureid = params["procedureid"];
this.requestdate = params["requestdate"];
this.animalqty = params["animalqty"];
this.marker = params["marker"];
this.method = params["method"];
this.fixative = params["fixative"];
this.handling = params["handling"];
this.processing = params["processing"];
// calling it now will at least make sure you attempted to set the data from params, but double check the params are actually returning data as well.
this.loadData();
});
// calling it here will execute before the above code
// this.loadData();
}
You can get more sophisticated with other RxJS operators, but this is a simple enough case it may not be worth complicating it just yet.

Show specific data for the clicked element using popover

when I retrieve data from a json file, in the *ngFor displays all the values in the popover, but I need a specific popover to display based only on the data for selected/clicked weapon. Here is my code any help would be greatly appreciated. Thank you again for your help
Home
import { Component } from '#angular/core';
import { NavController, ViewController, PopoverController, Events} from 'ionic-angular';
import { RestProvider } from './../../providers/rest/rest';
import { PopupPage } from './../../pages/popup/popup';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
weapons: any;
errorMessage: string;
constructor(public navCtrl: NavController, public rest: RestProvider,
public popoverCtrl: PopoverController) {
}
ionViewDidLoad() {
this.getweapons();
}
getweapons() {
this.rest.getweapons()
.subscribe(
weapons => this.weapons = weapons,
error => this.errorMessage = <any>error);
}
presentPopover(myEvent)
{
let popover = this.popoverCtrl.create(PopupPage);
popover.present({
ev: myEvent
});
}
}
home.html
<ion-content>
<ion-searchbar [(ngModel)]="terms"></ion-searchbar>
<ion-item>
</ion-item>
<ion-list>
<button ion-item (click)="presentPopover($event)">
<ion-item *ngFor="let c of weapons?.weapon_category?.weapons | search : terms">
<h2>{{c.name}}</h2>
</ion-item>
</button>
</ion-list>
</ion-content>
popup.ts
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams, ViewController, Events} from 'ionic-angular';
import { RestProvider } from './../../providers/rest/rest';
import { HomePage } from './../../pages/home/home';
/**
* Generated class for the PopupPage page.
*
* See https://ionicframework.com/docs/components/#navigation for more info on
* Ionic pages and navigation.
*/
#IonicPage()
#Component({
selector: 'page-popup',
templateUrl: 'popup.html',
})
export class PopupPage {
rangeSettings = 20;
weapons: any;
errorMessage: string;
constructor(public navCtrl: NavController, public navParams: NavParams, public viewCtrl: ViewController, public rest: RestProvider) {
alert('inside the popup');
}
close() {
this.viewCtrl.dismiss();
}
getweapons() {
this.rest.getweapons()
.subscribe(
weapons => this.weapons = weapons,
error => this.errorMessage = <any>error);
}
ionViewDidLoad() {
this.getweapons();
}
}
popup.html
<ion-header>
<ion-navbar>
<ion-title>popup</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item *ngFor="let c of weapons?.weapon_category?.weapons">
<h2>{{c.damage.base}}</h2>
<h2>{{c.damage.chest0}}</h2>
<h2>{{c.damage.chest1}}</h2>
<h2>{{c.damage.chest2}}</h2>
<h2>{{c.damage.chest3}}</h2>
<h2>{{c.damage.head1}}</h2>
<h2>{{c.damage.head2}}</h2>
<h2>{{c.damage.head3}}</h2>
</ion-item>
<ion-item>
<ion-range min="0" max="80" [(ngModel)]="rangeSettings" color="danger" pin="true" snaps="true" disabled=true></ion-range>
</ion-item>
</ion-list>
</ion-content>
Popover doesn't need to hit the REST call again. You can pass the chosen weapon to the popover as a parameter.
Change your function to accept a weapon (make sure you change the code in the HTML too)
presentPopover(myEvent, weapon)
And send it to the popover controller this way:
this.popoverCtrl.create(PopupPage, weapon);
Now in your popup.ts, decleare a weapon object in your class,
weapon : any;
and grab the weapon from the navParams in your constructor
this.weapon = this.navParams.data;
Change your <ion-item> in popup.html to display the selected one.
<ion-item>
{{weapon.damage.base}}
...
</ion-item>

How to include the user credentials in every sidebar of very pages in ionic

I want to have an sidebar that can display the name of the user who logged in. I have an account page that display all the details of the user. This is the code of the typescript below for the accounts page.
import { Component } from '#angular/core';
import { NavController, AlertController} from 'ionic-angular';
import {Headers} from "#angular/http";
import 'rxjs/add/operator/map';
import {Storage} from "#ionic/storage";
import { Global } from '../../providers/global';
#Component({
selector: 'page-account',
templateUrl: 'account.html',
})
export class AccountPage {
constructor(public navCtrl: NavController,
private storage: Storage,
public alertCtrl: AlertController,
public global: Global
)
{
//
}
public ACCOUNT_URL = this.global.url + "/api/inspectors";
credentials:any;
contentHeader = new Headers({"Content-Type": "application/json"});
error: any;
user: any;
token_type: any;
access_token: any;
refresh_token:any;
ionViewDidLoad() {
console.log('ionViewDidLoad AccountPage');
this.getAccessToken();
this.getAccount();
}
getAccessToken(){
this.storage.get('access_token').then((value) => {
this.access_token=value;
});
}
getAccount(){
this.storage.get('user').then((value) => {
this.user=value;
});
}
}
and this is the code in my html.
<ion-header>
<ion-navbar color="danger">
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Account</ion-title>
</ion-navbar>
</ion-header>
<ion-content class="page-account">
<ion-card>
<ion-item *ngIf="user">
<ion-card-content text-wrap>
<h2>Name: {{user.name}}</h2>
<p>{{user.cellphone_no}}</p>
<p> Address: {{user.address}} </p>
<p> Email: {{user.email}} </p>
</ion-card-content>
</ion-item>
</ion-card>
</ion-content>
What I want to happen is to delete the account pages and put the details of the user in top of the sidebar. So I have to put it into the app.html or app.ts but how can I define the property in my root component in order to display the details of that user.
Here is the code below in my app.component.ts
import { Component, ViewChild} from '#angular/core';
import { Nav, Platform } from 'ionic-angular';
import { StatusBar } from '#ionic-native/status-bar';
import { SplashScreen } from '#ionic-native/splash-screen';
import {Storage} from "#ionic/storage";
import { LoginPage } from '../pages/login/login';
import { AccountPage } from '../pages/account/account';
import { InspectionPage } from '../pages/inspection/inspection';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = LoginPage;
#ViewChild(Nav) nav: Nav;
pages: Array<{title: string, component: any, icon: string, color: string}>;
constructor(platform: Platform,
statusBar: StatusBar,
private storage: Storage,
splashScreen: SplashScreen) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
// used for an example of ngFor and navigation
this.pages = [
//{ title: 'Home', component: HomePage, icon: 'home', color: 'primary' },
{ title: 'Home', component: InspectionPage, icon: 'home', color: 'danger' },
{ title: 'Account', component: AccountPage, icon: 'person', color: 'primary' }
];
}
openPage(page) {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.nav.setRoot(page.component);
}
logout() {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.storage.remove('access_token');
this.storage.remove('username');
this.storage.remove('data');
this.storage.remove('user');
this.nav.setRoot(LoginPage);
}
}
and here is the code in my sidemenu or html.
<ion-menu [content]="content" id="myMenu">
<ion-header>
<ion-toolbar color="danger">
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
<ion-icon [name]="p.icon" [color]="p.color" item-left></ion-icon> {{p.title}}
</button>
<button menuClose ion-item (click)="logout()">
<ion-icon name="log-out" color="default" item-left></ion-icon> Logout
</button>
</ion-list>
</ion-content>
</ion-menu>
<!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
What I want to happen is to put the details of the user in the sidebar like what I did in the account page. Sorry for my long question. I tried searching for it but can't find any answer to it.
Looking for help.
Thanks in advance.
I would recommend that you create a service to handle the state of the user login. That way the state is handled in a central place and it will be much easier to maintain.
One possible approach would be to use a BehaviourSubject inside your service, which you can then subscribe to on every page that you need your user object (like your account page and your app component).
import {Injectable} from '#angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class UserService {
// Observable user object (replace any with your user class/interface)
private _userObject = new BehaviorSubject<any>({});
// Expose an observable that can be used by components
userObject$ = this._userObject.asObservable();
// Method to update the user
changeUser(user) {
this._userObject.next(user);
}
}
You can now use the service like that (you have to implement the subscription logic in every component where you want to have access to your user):
import {Component} from '#angular/core';
import {UserService} from './user.service';
#Component({
selector: 'account-page'
})
export class AccountPage {
user: any;
subscription: Subscription;
constructor(private _userService: UserService) {}
ngOnInit() {
this.subscription = this._userService.userObject$
.subscribe(item => this.user = item);
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
login() {
this._userService.changeUser({
name: 'Name' // Replace with name / user object
});
}
logout() {
this._userService.changeUser({});
}
}
As mentioned above, the big advantage is maintainability. If you ever change your user object, it only requires minimal changes, whereas the solution by #Tomislav Stankovic requires changes in every component where the user is used.
In your login page, when user is successfully logged-in, store data to localStorage
login(username,password){
this._api.userLogin().subscribe(res => {
if(res.status == 'ok'){
localStorage.setItem('user_first_name', res.user_first_name);
localStorage.setItem('user_last_name', res.user_last_name);
}
}
And then in app.component.ts get data from localStorage
this.first_name = localStorage.getItem('user_first_name');
this.last_name = localStorage.getItem('user_last_name');
Display data in app.html
<p>{{first_name}}</p>
<p>{{last_name}}</p>
On log-out clear localStorage
logout(){
localStorage.clear();
}

Share data between components using Service - IONIC 2, Angular 2

Is there any way to send data {{error.value}} to another page using a method?
This is my code
<ion-row *ngFor="let errors of adp_principal_menuRS">
<ion-col class="info-col" col-4>
<button ion-button color="primary" small (click)="goToErrors(errors.event)">
{{errors.event}}
</button>
</ion-col>
</ion-row>
goToErrors(menu: string){
console.log(menu);
this.navCtrl.push(AdpDetailPage, {
});
}
I want to send the {{errors.event}} value to another page in the goToErrors() method.
Thanks!
EDIT: I just achieve what I want. I edited the code
Data can be shared using BehaviorSubject between components via service.
Here is an example:
// service.ts
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ShareService {
private errorSource = new BehaviorSubject<any>(null);
error$ = this.errorSource.asObservable();
setError(error: any){
this.errorSource.next(error);
}
Set the error event in parent component using setError method and subscribe the error in error component.
// error component.ts
constructor(share: ShareService) {
share.error$.subscribe(Err => this.error = Err);
Why don't you send the value using a navParam?
goToErrors(menu: string){
console.log(menu);
this.navCtrl.push(AdpDetailPage, {
errorEvent: menu // <------------------------- Add this line
});
}
And in your AdpDetailPage:
export class AdpDetailPage{
constructor(public navParams: NavParams){
errorEvent = this.navParams.get('errorEvent');
console.log("errorEvent= ", errorEvent);
}
}
Use event emittor.
//Home component.ts import { Events } from 'ionic-angular'; constructor(public events: Events) {} directioChange(user) {this.events.publish('directiochanged', 'true');} //App.component.ts constructor(public events: Events) { events.subscribe('directiochanged', (direction) => { this.isRtl = direction;console.log(direction);});}
I generated a Plunker that hopefully matches with what you are trying to do.
https://plnkr.co/edit/MNqpIqJjp5FN30bJd0RB?p=preview
Service
import { Injectable } from '#angular/core';
#Injectable()
export class ErrorService {
errorInfo: string;
}
Component
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
<button (click)="goToErrors()">{{errors.event}} </button>
<router-outlet></router-outlet>
</div>
`,
})
export class App {
name:string;
errors = { event: 'Test Error', otherInfo: 'Test Info' };
constructor(private errorService: ErrorService, private router: Router) {
this.name = `Angular! v${VERSION.full}`
}
goToErrors(): void {
// Code to navigate to the other component
this.errorService.errorInfo = this.errors.event;
this.router.navigate(['/a']);
}
}

Ionic 2 will not let me loop over an object of objects using *ngFor

I am developing an app where users can search for classified ads.
However, I am unable to display the search results.
Here is my search_results.ts:
import {Component} from '#angular/core';
import {NavController, NavParams} from 'ionic-angular';
import {Http} from '#angular/http';
import 'rxjs/add/operator/map';
#Component({
selector: 'page-page1',
templateUrl: 'search_results.html' })
export class SearchResults {
public term: any;
public ads: any;
public tmp: any;
constructor(public navCtrl: NavController, public params: NavParams, public http: Http) {
this.term = this.params.get('term');
this.ads = [];
this.tmp = [];
console.log("this.term", this.term);
this.http.get('http://www.truckers.host/app/search-ads.php')
.map(res => res.json()).subscribe(data => {
console.log("this is returned from http.get", data);
});
}
ngOnInit() {
// Let's navigate from TabsPage to Page1
console.log(this.params);
} }
Now, in my search_results.html to display the search results, I am not getting anything to print.
Here is that code:
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>
<img src="assets/images/trucker-to-trucker-logo.png" />
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding class="card-background-page">
<ion-card>
<ion-card-header>
Search Results
</ion-card-header>
<ion-card-content>
Searching for: <b>{{ term }}</b>
</ion-card-content>
</ion-card>
<ion-card>
<ion-list>
<ion-item *ngFor="let ad of ads">
{{ ad.ad_id }}
</ion-item>
</ion-list>
</ion-card>
</ion-content>
Nothing is returned, or iterated over.
When I console.log(data), I get an Object of Objects, and Ionic does not want to loop them.
Please let me know what other information I should include to help.
*ngFor only supports for Array and not Json Object.
You should consider using a custom Pipe to convert Object to Array.
Documentation about Pipe.
#Pipe({name: 'toArray'})
export class ToArrayPipe implements PipeTransform {
transform(inputObj: any, arg: any) {
if (!inputObj) { return [] }
let arr = [];
for(let key in inputObj) {
// Option1 (only value without the json object's key)
// this way will lose the key of Json Object
//arr.push(inputObj[key]);
// OPtion2 (both the key and value)
let obj = {};
obj[key] = inputObj[key];
arr.push(obj);
}
return arr;
}
}
Then add it to declarations of NgModule, and use it in your temple this way:
<ion-item *ngFor="let ad of ads | ToArray">
...
</ion-item>