load data in the view before loading it [duplicate] - ionic-framework

This question already has an answer here:
Error if don't check if {{object.field}} exists
(1 answer)
Closed 6 years ago.
I have a problem, I am working with Ionic 2 and angular 2.
I have to show some datas in a view but I get these datas from an API in the same page so when I try to show them in the view, they are undefined yet. How can I wait to load the view after getting these datas?
I have tried onPageWillEnter but it does not work.
Thank you in advance.

You can wrap your template with *ngIf
template: `
<div *ngIf="data">
... <!-- content -->
</div>`
When data is set, the content will be shown.
You can also use the safe-navigation or Elvis operator to avoid error messages
template: `<div>{{data?.someProp}}</div>`
to avoid error messages when data is null

provider allow you get data from server
generate: ionic generate provider MyProvider --ts
#Injectable()
export class MenuProvider {
data: any = null;
constructor(public http: Http) { }
load() {
if (this.data) {
// already loaded data
return Promise.resolve(this.data);
}
// don't have the data yet
return new Promise(resolve => {
// We're using Angular Http provider to request the data,
// then on the response it'll map the JSON data to a parsed JS object.
// Next we process the data and resolve the promise with the new data.
this.http.get('asset/menu.json')
.map(res => res.json())
.subscribe(data => {
// we've got back the raw data, now generate the core schedule data
// and save the data for later reference
this.data = data;
resolve(this.data);
});
});
}
}
menu.ts
import {MenuProvider} from "../../providers/menu-provider/menu-provider";
import {Component, OnInit} from '#angular/core';
import {IONIC_DIRECTIVES, NavController, Modal} from 'ionic-angular';
import {AccountHistoryPage} from "../../pages/account-history/account-history";
/*
Generated class for the MenuComponent component.
See https://angular.io/docs/ts/latest/api/core/ComponentMetadata-class.html
for more info on Angular 2 Components.
*/
#Component({
selector: 'menu-component',
templateUrl: 'build/components/menu-component/menu-component.html',
directives: IONIC_DIRECTIVES,
providers: [MenuProvider]
})
export class MenuComponent {
// menu object
jsonMenu: any;
constructor(public nav: NavController, menuProvider: MenuProvider) {
// TODO: show progress
menuProvider.load().then(data => {
// TODO: turn off menu load json progress
// data after proccessed.
this.jsonMenu = data;
console.log(this.jsonMenu);
}, err => console.log(err));
}
itemClick(moduleNumber: number) {
console.log(moduleNumber);
let page: any = null;
if (moduleNumber == 210102) {
page = Modal.create(AccountSourcePage);
}
if (moduleNumber == 210103) {
page = Modal.create(AccountBeneficiatyPage);
}
if (moduleNumber == 210101) {
page = Modal.create(AccountHistoryPage);
}
this.nav.present(page);
}
}
after that use temple with ngFor, ngIf.... to display
<ion-item *ngFor="let menu of jsonMenu">
<!-- title -->
<div class="khungtieude">
<span class="tieudechinh">{{menu.title}}</span>
<span class="gachgiua"></span>
</div>
<!-- arrows -->
<div class="arrow-container" [hidden]="!(menu.contains.length > 1)">
<ion-icon class="arrow-back" name="ios-arrow-back"></ion-icon>
<ion-icon class="arrow-forw" name="ios-arrow-forward"></ion-icon>
</div>
<!-- slide -->
<!-- using template instead of fix code, i will add later -->
<ion-slides loop>
<!-- page 1 of side -->
<!-- i need loop in total/3 + (total%3==0? 0 : 1) -->
<ion-slide *ngFor="let temp of menu.contains">
<ion-row style="padding-top: 10px;">
<ion-col width-33 center *ngFor="let menuItem of temp.page" (click)="itemClick(menuItem.moduleId)">
<span class="icon-cknb main-menu-ic"></span>
<br/>
<div class="main-menu-text">
{{menuItem.displayName}}
</div>
</ion-col>
</ion-row>
</ion-slide>
</ion-slides>
</ion-item>

Related

I cannot querySelect input of ion-searchbar in Jasmine tests

I'm running a simple test - enter a value into an Ionic search bar.
I cannot get a reference to the search bar input field in my jasmine tests.
I've set up a simple vanilla Ionic 4 app with a single test page.
The following tests all fail.
const el: HTMLElement = fixture.nativeElement;
const input = el.querySelector('input');
expect(input).not.toBeNull(); // FAILS
const bar = el.querySelector('ion-searchbar');
expect(bar.innerHTML).not.toEqual(''); // FAILS
expect(bar.shadowRoot).not.toBeNull(); // FAILS
I don't understand why?
I've written other tests successfully on Ionic lists to read values.
I tried putting in timers to give the component time to render the html.
If I put a simple HTML input field on the page it is picked up OK.
I thought the shadow DOM might be relevant here even though when I inspect the input field in the browser it doesn't seem to be in the shadow DOM, and the standard document.querySelector works just fine.
HTML Page
// test.page.html
<ion-header>
<ion-toolbar>
<ion-title>
Test Page
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-searchbar></ion-searchbar>
</ion-content>
Component Class
// test.page.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-test',
templateUrl: './test.page.html',
styleUrls: ['./test.page.scss'],
})
export class TestPage {
constructor() { }
}
Test Spec
// test.page.spec.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { TestPage } from './test.page';
describe('TestPage', () => {
let component: TestPage;
let fixture: ComponentFixture<TestPage>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestPage ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestPage);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have a searchbar', () => {
const el: HTMLElement = fixture.nativeElement;
const bar = el.querySelector('ion-searchbar');
fixture.detectChanges();
expect(bar).not.toBeNull(); // PASSES
// adding some checks to see what is going on
expect(bar.children.length).toBeGreaterThan(0); // FAILS
expect(bar.innerHTML).not.toEqual(''); // FAILS
expect(bar.shadowRoot).not.toBeNull(); // FAILS
});
it('should have an input field', () => {
const el: HTMLElement = fixture.nativeElement;
const input = el.querySelector('input');
expect(input).not.toBeNull(); // FAILS
});
});
This is over a year late, but I think it's worth answering for others who might need it. For what it's worth, I'm running #ionic-native/core 4.15 with #angular/core 5.2.11 and ionic-angular 3.9.2.
I don't know why const input = el.querySelector('input'); isn't working for you. The following code works for me:
const toolbar = fixture.nativeElement.querySelector('ion-toolbar')
const searchBar = toolbar.querySelector('ion-searchbar');
const input = searchBar.querySelector('input');
Alternatively, you can make your query for the input more specific:
const input = searchBar.querySelector('div.searchbar-input-container > input.searchbar-input')
I like to look at the structure of HTMLElements and their component instances by console.loging them in the test. When I add this to my test:
console.log(searchBar);
The console in the captured test browser will output:
<ion-searchbar class="searchbar searchbar-md searchbar-left-aligned" ng-reflect-placeholder="Besmirch">
<div class="searchbar-input-container">
<button class="searchbar-md-cancel button button-md button-clear button-clear-md button-clear-md-dark" clear="" color="dark" ion-button="" mode="md" type="button" ng-reflect-color="dark" ng-reflect-mode="md" ng-reflect-clear=""><span class="button-inner"><ion-icon name="md-arrow-back" role="img" class="icon icon-md" aria-label="arrow back" ng-reflect-name="md-arrow-back"></ion-icon></span>
<div class="button-effect"></div>
</button>
<div class="searchbar-search-icon"></div>
<input class="searchbar-input" dir="auto" placeholder="Besmirch" type="search" autocomplete="off" autocorrect="off" spellcheck="false">
<button class="searchbar-clear-icon button button-md button-clear button-clear-md" clear="" ion-button="" type="button" ng-reflect-mode="md" ng-reflect-clear=""><span class="button-inner"></span>
<div class="button-effect"></div>
</button>
</div>
<button class="searchbar-ios-cancel button button-ios button-clear button-clear-ios" clear="" ion-button="" mode="ios" type="button" ng-reflect-mode="ios" ng-reflect-clear="" tabindex="-1"><span class="button-inner">Cancel</span>
<div class="button-effect"></div>
</button>
</ion-searchbar>
In this structure, we can see the input is in the .searchbar-input-container div.

How to show feeds in the same page after applying filters in ionic

i am creating an Listing ionic application where i am displaying feeds from an API in the home page. It also has some filter options which is an actionsheet with options such as near by, populer and so on. What i want to do is when user click one of the filter for example populer, i want to do display the new feeds on the same page. How can i do so ??
My code base is as below
home.ts
constructor(....){
this.getFeeds();
}
//make api call to get the feeds
getFeeds(){
const data = localStorage.getItem('userToken');
this.userPostData.api_token= data;
this.authService.postData(this.userPostData,'feeds').then((result)=>{
this.responseData = result;
})
}
//Feeds by location
//Actionsheet
filters() {
let actionSheet = this.actionSheetCtrl.create({
title: 'Sort Events by',
buttons: [
{
text: 'Location',
icon:'pin',
handler: () => {
//Make api call to feeds based on location
}
},
{
text: 'Popularity',
icon:'people',
handler: () => {
//Make api call to feeds based on location
}
}
]
});
actionSheet.present();
}
And my home.html is
<ion-card *ngFor="let item of responseData?.feed" tappable (click)="viewDetail(item.id)">
<div class="event-image-holder search-list">
<img src="http://localhost:8000/{{item.photo_url}}"/>
<div class="event-attendee-count">
<ion-icon name="people"></ion-icon> {{item.attendees.length}} are going
</div>
</div>
<ion-card-content>
<div class="event-info">
<div class="event-time">
<!-- 04 Feb -->
{{item.gmt_date_set | date:'dd MMM'}}
</div>
<div class="event-descp">
<h2>{{item.title}}</h2>
<p>
{{item.club.name}}
</p>
</div>
</div>
</ion-card-content>
</ion-card>
After some research here and there tutorials from youtube and udemy, i found that when ever you change the data of the variable it automatically change on the view page.
So in my case if i override the data of this.responseData it will automatically updated on the view page with out much doing.

data:image location in ionic3 app

i am making image upload using ionic 3 app and FileReader , after the image is uploaded i get this as image url
data:image/jpeg;base64,/4AAQSkZJRgABAQ...9oADAMBAAIRAxEAPwD/AD/6AP/Z
something like this , then i store it as is into my database
i noticed that if i open the app at another device the image will still be loaded and show in the view . so i wounder where does this image go after i upload it? where is it saved in my app ? i tried to look into my app folders couldn't find the img
thanks,
Here is the code i am using
<ion-header>
<ion-navbar>
<ion-title>{{ 'ITEM_CREATE_TITLE' | translate }}</ion-title>
<ion-buttons start>
<button ion-button (click)="cancel()">
<span color="primary" showWhen="ios">
{{ 'CANCEL_BUTTON' | translate }}
</span>
<ion-icon name="md-close" showWhen="android,windows"></ion-icon>
</button>
</ion-buttons>
<ion-buttons end>
<button ion-button (click)="done()" [disabled]="!isReadyToSave" strong>
<span color="primary" showWhen="ios">
{{ 'DONE_BUTTON' | translate }}
</span>
<ion-icon name="md-checkmark" showWhen="core,android,windows"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
Ts file:
export class ItemCreatePage {
#ViewChild('fileInput') fileInput;
isReadyToSave: boolean;
item: any;
form: FormGroup;
constructor(public navCtrl: NavController, public viewCtrl: ViewController, formBuilder: FormBuilder) {
this.form = formBuilder.group({
profilePic: [''],
name: ['', Validators.required],
about: ['']
});
// Watch the form for changes, and
this.form.valueChanges.subscribe((v) => {
this.isReadyToSave = this.form.valid;
});
}
ionViewDidLoad() {
}
getPicture() {
this.fileInput.nativeElement.click();
}
processWebImage(event) {
let reader = new FileReader();
reader.onload = (readerEvent) => {
let imageData = (readerEvent.target as any).result;
this.form.patchValue({ 'profilePic': imageData });
};
reader.readAsDataURL(event.target.files[0]);
}
getProfileImageStyle() {
return 'url(' + this.form.controls['profilePic'].value + ')'
}
/**
* The user cancelled, so we dismiss without sending data back.
*/
cancel() {
this.viewCtrl.dismiss();
}
/**
* The user is done and wants to create the item, so return it
* back to the presenter.
*/
done() {
if (!this.form.valid) { return; }
this.viewCtrl.dismiss(this.form.value);
}
}

Ionic 2: View does not update based on model change

I have a very simple page with a couple of controls.
My issue is that the page does not pickup changes to the model when the icon in upper right corner is clicked. This toggles the showFilterPane variable, which again should show or hide a div based on *ngIf="showFilterPane".
I have another page just like this one working, and I can not figure out why this isn't.
Any tips?
(I've tried using the ChangeDetectorRef.detectChanges(); which works, but then the rangeslider will not work. The draggable point doesn't update, or does not move to where you tap.)
The page:
<ion-header>
<ion-navbar>
<ion-title>MY AO</ion-title>
<ion-buttons end>
<button ion-button (click)="toggleFilterPane()" icon-only>
<ion-icon name="options"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<div class="container">
<div class="left">
<div *ngIf="isSearching" class="spinner-container">
<ion-spinner></ion-spinner>
</div>
<!-- put content here -->
</div>
<div class="right" *ngIf="showFilterPane">
<ion-list inset>
<ion-list-header>BANA</ion-list-header>
<ion-item>
<ion-select multiple="true" [(ngModel)]="woTrackFilter">
<ion-option>1</ion-option>
<ion-option>2</ion-option>
<ion-option>3</ion-option>
<ion-option>4</ion-option>
<ion-option>5</ion-option>
</ion-select>
</ion-item>
</ion-list>
<ion-list inset>
<ion-list-header>TEKNIKSLAG</ion-list-header>
<ion-item>
<ion-select multiple="true" [(ngModel)]="woDisciplineFilter">
<ion-option>Signal</ion-option>
<ion-option>Bana</ion-option>
<ion-option>EL</ion-option>
<ion-option>Tele</ion-option>
</ion-select>
</ion-item>
</ion-list>
<ion-list inset>
<ion-list-header>DAGAR</ion-list-header>
<ion-item>
<ion-range min="10" max="80" step="4" [(ngModel)]="woDaysFilter">
<ion-label range-left>10</ion-label>
<ion-label range-right>80</ion-label>>
</ion-range>
</ion-item>
</ion-list>
<button ion-button (click)="doSearch()">Search</button>
</div>
</div>
</ion-content>
The component:
import { Component } from '#angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { WorkOrderDashboardPage } from "../work-order-dashboard/work-order-dashboard";
#Component({
selector: 'page-work-order-list',
templateUrl: 'work-order-list.html'
})
export class WorkOrderListPage {
private isSearching: boolean = false;
private showFilterPane: boolean=false;
private woTrackFilter: string[];
private woDisciplineFilter: string[];
private woDaysFilter: number;
constructor(public navCtrl: NavController, public navParams: NavParams) {
// Initialize storage providers here
}
ionViewDidLoad() {
console.log('ionViewDidLoad WorkOrderListPage');
}
toggleFilterPane(): void {
this.showFilterPane = !this.showFilterPane;
}
viewWorkOrder(event, workOrder): void {
this.navCtrl.push(WorkOrderDashboardPage, { workOrder: workOrder });
}
doSearch(): void {
console.log(this.woTrackFilter);
console.log(this.woDisciplineFilter);
console.log(this.woDaysFilter);
}
}
UPDATE: Found workaround
I tried creating a separate app, where the exact same code is working. That lead me to think something wasn't right on the LoginPage, the page that called setRoot() to the above page.
The login code looked like this:
WLAuthorizationManager.login("UserLogin", data).then(() => {
// Success
console.log("Logged in");
this.navCtrl.setRoot(WorkOrderListPage);
},
(err) => {
// failed
console.error(err);
this.showError("Username or password is incorrect");
})
I then figured it might be some Zone issue, and wrapped the setRoot call in zone.run() like this:
WLAuthorizationManager.login("UserLogin", data).then(() => {
// Success
console.log("Logged in");
this.zone.run(() =>
this.navCtrl.setRoot(WorkOrderListPage)
);
},
(err) => {
// failed
console.error(err);
this.showError("Username or password is incorrect");
})
After that the view started to respond as expected. I feel this is a bit of a hack. Can someone shed some light as to what is happening here?
Seems like you are basically using ngZone to make sure Angular knows you changed things so it will reload that part of the DOM to reflect the changes. I don't feel like it's a hack, because you are just making sure that it works as intended.
Angular 2 has some optimization features that help make your app run smoother and one of those is avoiding DOM updates whenever and wherever possible. By using zones (or ngZones) you are basically telling Angular "pay attention to this part, it changes and I need that change to be reflected in the DOM".
I've run into that sort of problem before myself and using zones is usually your best bet. Got into situations where a part of the interface would be stuck unless you touched a button or somesuch.
Another workaround (at least for range sliders) is using the AppllicationRef tick() method, which forces a DOM update. More info about it here.

Ionic 2 ion-card getElementById returns null

I have an ion-card list of elements in a page and I want to navigate to a specific position when entering the view, the page renders properly and I can see in the generated code that div with specific id is indeed on the view but trying to get the element by id is returning null.
Here is the code for the view where I build the cards:
<ion-content class="cards-bg">
<ion-card *ngFor="let meal of meals">
<ion-card-header>
{{meal.date | date: 'dd/MM/yyyy HH:mm'}}
</ion-card-header>
<div id="{{meal.id}}">
<img src="{{serverUrl + '/' + meal.picture}}">
</div>
<ion-card-content>
<ion-card-title>
{{meal.title}}
</ion-card-title>
<p>{{meal.description}}</p>
</ion-card-content>
</ion-card>
<ion-row no-padding></ion-row>
</ion-content>
And this is the code that tries to get the reference to the element so I can navigate to it:
public index: string;
constructor(public navCtrl: NavController, navParams: NavParams, public mealService: MealService) {
this.index = navParams.get('index');
}
ionViewDidEnter() {
this.mealService.getMeals().then((result) => {
this.meals = result;
let yOffset = document.getElementById(this.index).offsetTop;
this.content.scrollTo(0, yOffset, 1000);
}).catch((error) => {
console.log(error);
});
As I mentioned the page with cards render, I can even see the generated code contains the specific div with id but the call:
document.getElementById(this.index)
returns null, so I get the following error in logs:
chromium: [INFO:CONSOLE(57687)] "TypeError: Cannot read property 'offsetTop' of null", source: file:///android_asset/www/build/main.js (57687)
Use Angular`s ElementRef
Inject it in constructor:
constructor(private elRef:ElementRef){}
For getting the element,
let el = this.elRef.nativeElement;//get underlying native element.
let yOffset = el.getElementById(this.index).offsetTop;
Or you could also use ViewChildren:
Set an id for the div:
<div #divElement id="{{meal.id}}">
<img src="{{serverUrl + '/' + meal.picture}}">
</div>
Set the decorator on a class variable:
#ViewChildren('divElement')
divs:any;
For accessing elements,
let yOffset = this.divs[this.index].nativeElement.offsetTop