Display Data from Backend in Ionic - php

I have a user.service, a list.page.ts and a list.page.html.
And I want to display data from the backend (php). I am getting Data in the console log, but it doesn't display on the website. Please Help.
Thank you!
user.servive
export enum SearchType {
all = '',
name = 'name',
stadt = 'stadt'
}
#Injectable({
providedIn: 'root'
})
export class UserService {
url = 'http://127.0.0.1:8000/getallrestaurants';
constructor(public http: HttpClient) { }
searchData(name: string, type: SearchType): Observable<any> {
return this.http.get('http://127.0.0.1:8000/getallrestaurants')
.pipe(
map(results => {
console.log('RAW: ', results);
return results['Search'];
})
);
}
list.page.ts
export class ListPage implements OnInit {
results: Observable<any>;
searchTerm = '';
type: SearchType = SearchType.all;
constructor(private userService: UserService) { }
ngOnInit() {}
searchChanged() {
this.results = this.userService.searchData(this.searchTerm, this.type);
}
list.page.html
<ion-searchbar [(ngModel)]="searchTerm" (ionChange)="searchChanged($event)"></ion-searchbar>
<ion-item>
<ion-label>Select Searchtype</ion-label>
<ion-select [(ngModel)]="type" (ionChange)="searchChanged($event)">
<ion-select-option value="">All</ion-select-option>
<ion-select-option value="stadt">Stadt</ion-select-option>
<ion-select-option value="name">Name</ion-select-option>
</ion-select>
</ion-item>
<ion-list>
<ion-item button *ngFor="let item of (results | async)" [routerLink]="['/', 'list', item.imdbID ]">
<ion-icon [name]="item.icon" slot="start"></ion-icon>
<p>Hallo User: {{item.name}}</p>
</ion-item>
This is my console.log, as you see i get the data, now i want it to show for example the name on frontend.

Ok I see the problem here in the class user.service you have to change it like this.
searchData(name: string, type: SearchType): Observable<any> {
return this.http.get('http://127.0.0.1:8000/getallrestaurants')
.pipe(
map(results => {
console.log('RAW: ', results);
return results;
})
);
}
You have to remove the ['Search'] because your JSON Response has no entry which is like 'Search'.

If you're seeing console.log update then this is most likely an issue with the html template not knowing some data changed that it needs to re-render and update the view for. Angular has built in change detection strategy but as the developer you can also manually interact with it. Checkout (https://angular.io/api/core/ChangeDetectorRef) for further explanation on this topic.
As for your code, try inserting the change detection reference as so by importing ChangeDetectorRef from #angular/core, and then check for changes of data to tell the view to re-render.
list.page.ts
export class ListPage implements OnInit {
results: Observable<any>;
searchTerm = '';
type: SearchType = SearchType.all;
constructor(private userService: UserService, private ref: ChangeDetectorRef) { }
ngOnInit() {}
searchChanged() {
this.results = this.userService.searchData(this.searchTerm, this.type);
this.ref.detectChanges()
}
Note: this will execute every time the user types and it may slow down your application because you're forcing angular to check for changes every time you call searchChanged(). I would thereby implement some sort of debounce technique to stall redundant or unnecessary calls to searchChanged(). Check out this resources to learn more about debouncing if this issue comes up (https://rxjs-dev.firebaseapp.com/api/operators/debounce).

Related

use dynamic params for axios requests

axios.get("/api/session/" + this.roomId)
Above is a snippet from my axios call that uses my api.php route ('/api/session/{id} that loads through the controller the requested specific room => /api/session/3 is room 3).
Currently, this snippet is harcoded and always uses integer 1 for 'this.roomId'.
I did that, in oder to check if my vue is even working fine.
My question is now, how am I able to use a dynamic param for my prop roomId?
so I can always say something like
.get/.post('url', $id) ?
If you're passing the roomId as a prop into the component then you need to handle the change in the parent component. For that I'd need a bit more context on where the room-ids come from and how you select the room-id there.
If you have this part down, then you'll want to watch changes on the roomId prop and re-fetch the data when ever it changes. You can do something like this in your room component:
<script>
import axios from 'axios'
const props: {
roomId: {
type: Number,
required: true
}
}
export default {
props,
data() {
return {
room: null
}
},
methods: {
async fetchRoom() {
try {
const response = await axios.get(`/api/session/${this.roomId}`)
this.room = response.data
} catch (err) {
// - handle error
}
}
},
watch: {
roomId: {
immediate: true // so it's executed when component is created
handler: function () {
this.fetchRoom()
}
}
}
}
</script>

Data don't load at the first time

I'm developing an Ionic app with angular. It has an API with php. So, I've a service that returns a JSON, the thing is, that the first time it loads the page, it doesn't load the data from the JSON.
The service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpClientModule } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class PlayerService {
posts;
baseURL: String;
constructor(public http: HttpClient) {
this.baseURL = 'http://127.0.0.1:8000/'
}
getPlayers() {
this.http.get(this.baseURL + 'GetPlayers')
.subscribe(data => {
this.posts = data;
});
return this.posts;
}
How I load it:
#Component({
selector: 'app-players',
templateUrl: './players.page.html',
styleUrls: ['./players.page.scss'],
})
export class PlayersPage implements OnInit {
constructor(private playerService: PlayerService) { }
players = this.playerService.getPlayers();
ngOnInit() {
}
}
HTML:
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Players</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item *ngFor="let p of players">
<ion-label>
<h2>{{p.name}}</h2>
<h3>{{p.mail}}</h3>
</ion-label>
</ion-item>
</ion-list>
</ion-content>
You should be returning the observable on your component, instead of the service. This will ensure the data is loaded upon initialisation of the component.
#Component({
selector: 'app-players',
templateUrl: './players.page.html',
styleUrls: ['./players.page.scss'],
})
export class PlayersPage implements OnInit {
constructor(private playerService: PlayerService) { }
ngOnInit() {
this.playerService.getPlayers().subscribe(data => {
this.players = data;
});
}
}
And on your service.ts, make the following changes.
getPlayers() {
return this.http.get(this.baseURL + 'GetPlayers');
}
Do try to understand the purpose of services, and components. As stated on the Angular documentation,
Ideally, a component's job is to enable the user experience and
nothing more. A component should present properties and methods for
data binding, in order to mediate between the view (rendered by the
template) and the application logic (which often includes some notion
of a model).
On the other hand, the duty of fetching and saving data (carrying out of HTTP requests) should be delegated to services.

How to pass Laravel data into Vue root instance with Laravel Mix Setup

So I am using Laravel 5.5. I have a data coming from my Controller and I want to pass it to my root vue instance not the component.
So for example I have the Dashboard Controller which has a data of "users"
class DashboardController extends Controller {
public function index(){
$user = User::find(1);
return view('index', compact('user'));
}
}
I am using Larave mix on my project setup. So my main js file is the app.js. That "$user" data I need to pass on the root Vue instance. Which is located in app.js
const app = new Vue({
el: '#dashboard',
data: {
// I want all the data from my controller in here.
},
});
If you don't want to use an API call to get data (using axios or else), you could simply try this :
JavaScript::put(['user' => $user ]);
This will, by default, bind your JavaScript variables to a "footer" view. You should load your app.js after this footer view (or modify param bind_js_vars_to_this_view).
In app.js :
data: {
user: user
}
Read more : https://github.com/laracasts/PHP-Vars-To-Js-Transformer
I would make a request to fetch the user's data as has been suggested.
Alternatively, you can add a prop to the dashboard component in index.blade.php and set the user like <dashboard :user="{{ $user }}"></dashboard>. You'll probably want to json_encode or ->toArray() the $user variable.
Then within the dashboard component you can set data values based on the prop.
props: ['user'],
data () {
return {
user: this.user
}
}
I just solved this by placing a reference on the window Object in the <head> of my layout file, and then picking that reference up with a mixin that can be injected into any component.
TLDR SOLUTION
.env
GEODATA_URL="https://geo.some-domain.com"
config/geodata.php
<?php
return [
'url' => env('GEODATA_URL')
];
resources/views/layouts/root.blade.php
<head>
<script>
window.geodataUrl = "{{ config('geodata.url') }}";
</script>
</head>
resources/js/components/mixins/geodataUrl.js
const geodataUrl = {
data() {
return {
geodataUrl: window.geodataUrl,
};
},
};
export default geodataUrl;
usage
<template>
<div>
<a :href="geodataUrl">YOLO</a>
</div>
</template>
<script>
import geodataUrl from '../mixins/geodataUrl';
export default {
name: 'v-foo',
mixins: [geodataUrl],
data() {
return {};
},
computed: {},
methods: {},
};
</script>
END TLDR SOLUTION
If you want, you can use a global mixin instead by adding this to your app.js entrypoint:
Vue.mixin({
data() {
return {
geodataUrl: window.geodataUrl,
};
},
});
I would not recommend using this pattern, however, for any sensitive data because it is sitting on the window Object.
I like this solution because it doesn't use any extra libraries, and the chain of code is very clear. It passes the grep test, in that you can search your code for "window.geodataUrl" and see everything you need to understand how and why the code is working.
That consideration is important if the code may live for a long time and another developer may come across it.
However, JavaScript::put([]) is in my opinion, a decent utility that can be worth having, but in the past I have disliked how it can be extremely difficult to debug if a problem happens, because you cannot see where in the codebase the data comes from.
Imagine you have some Vue code that is consuming window.chartData that came from JavaScript::put([ 'chartData' => $user->chartStuff ]). Depending on the number of references to chartData in your code base, it could take you a very long time to discover which PHP file was responsible for making window.chartData work, especially if you didn't write that code and the next person has no idea JavaScript::put() is being used.
In that case, I recommend putting a comment in the code like:
/* data comes from poop.php via JavaScript::put */
Then the person can search the code for "JavaScript::put" and quickly find it. Keep in mind "the person" could be yourself in 6 months after you forget the implementation details.
It is always a good idea to use Vue component prop declarations like this:
props: {
chartData: {
type: Array,
required: true,
},
},
My point is, if you use JavaScript::put(), then Vue cannot detect as easily if the component fails to receive the data. Vue must assume the data is there on the window Object at the moment in time it refers to it. Your best bet may be to instead create a GET endpoint and make a fetch call in your created/mounted lifecycle method.
I think it is important to have an explicit contract between Laravel and Vue when it comes to getting/setting data.
In the interest of helping you as much as possible by giving you options, here is an example of making a fetch call using ES6 syntax sugar:
routes/web.php
Route::get('/charts/{user}/coolchart', 'UserController#getChart')->name('user.chart');
app/Http/Controllers/UserController.php
public function getChart(Request $request, User $user)
{
// do stuff
$data = $user->chart;
return response()->json([
'chartData' => $data,
]);
}
Anywhere in Vue, especially a created lifecycle method:
created() {
this.handleGetChart();
},
methods: {
async handleGetChart() {
try {
this.state = LOADING;
const { data } = await axios.get(`/charts/${this.user.id}/coolchart`);
if (typeof data !== 'object') {
throw new Error(`Unexpected server response. Expected object, got: ${data}`);
}
this.chartData = data.chartData;
this.state = DATA_LOADED;
} catch (err) {
this.state = DATA_FAILED;
throw new Error(`Problem getting chart data: ${err}`);
}
},
},
That example assumes your Vue component is a Mealy finite state machine, whereby the component can only be in one state at any given time, but it can freely switch between states.
I'd recommend using such states as computed props:
computed: {
isLoading() { return (this.state === LOADING); },
isDataLoaded() { return (this.state === DATA_LOADED); },
isDataFailed() { return (this.state === DATA_FAILED); },
},
With markup such as:
<div v-show="isLoading">Loading...</div>
<v-baller-chart v-if="isDataLoaded" :data="chartData"></v-baller-chart>
<button v-show="isDataFailed" type="button" #click="handleGetChart">TRY AGAIN</button>

Delete data in Angular2 app in PHP

How would one delete data from a MySql database using PHP code in an Angular2 application? The closest advice is for Angular 1 and is as follows:
$scope.deleteProduct = function(id){
// ask the user if he is sure to delete the record
if(confirm("Are you sure?")){
// post the id of product to be deleted
$http.post('delete_product.php', {
'id' : id
}).success(function (data, status, headers, config){
// tell the user product was deleted
Materialize.toast(data, 4000);
// refresh the list
$scope.getAll();
});
}
}
Is it possible to use the post method similarly:
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import 'rxjs/Rx';
#Injectable()
export class HttpService {
constructor(private http: Http) {}
deleteData() {
return this.http.post('delete_record.php')
}
}
Any insight/experience with Angular2/PHP would be appreciated.
Yes, the http post works similarly in angular2. Since you want to use post, i guess you also want to add a body to the request.
import { Injectable } from 'angular/core';
import { Http } from 'angular/http';
#Injectable()
export class HttpService {
constructor(private http: Http) {}
deleteData(data: SomeObject) {
let url = "delete_record.php";
let body = JSON.stringify(data);
return this.http.post(url, body)
.subscribe(
result => console.log(result),
error => console.error(error)
);
}
}
You can also send a delete request, which would be "best practice".
return this.http.delete(url)
.subscribe(
result => console.log(result),
error => console.error(error)
});
More about the http-client here https://angular.io/docs/ts/latest/guide/server-communication.html

angular2 http requist handling chunke of responses

While I'm experimenting with angular2 a small obstacle came up:
I have php code witch returns chunks of responses using "ob_flush".
In the front end I successfully made "xhr=XMLHttpRequest" requests and received the responses and handle it using "xhr.onprogress()" and "xhr.onreadystatechange()".
Now when I tried to get the same functionality using angular2 http.get(), I couldn't output the results as they arrive from the server! instead the results are shown by angular at the end of the process after receiving the last response.
I think the rxjs Observer object is buffering the responses!.
So how can I change this behavior?
here is my php code, testing.php:
echo date('H:i:s')." Loading data!";
ob_flush();
flush();
sleep(5);
echo "Ready to run!";
here is my angular2 code:
template: `
<div>
<h3>experimenting!</h3>
<button (click)="callServer()">run the test</button>
<div>the server says: {{msg}}</div>
</div>`
export class AppComponent {
msg:any;
constructor (private http:Http){}
callServer(){
this.http.get("localhost/testing.php")
.subscribe(res=> this.msg= res.text());
}
}
When I run this code it shows after 5 seconds:
(19:59:47 Loading data!Ready to run!).
It should instantly output: (19:59:47 Loading data!).
Then after 5 seconds replaces the previous message with:(Ready to run!)
You need to extend the BrowserXhr class to do that in order to configure the low level XHR object used:
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor(private service:ProgressService) {}
build(): any {
let xhr = super.build();
xhr.onprogress = (event) => {
service.progressEventObservable.next(event);
};
return <any>(xhr);
}
}
and override the BrowserXhr provider with the extended:
bootstrap(AppComponent, [
HTTP_PROVIDERS,
provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);
See this question for more details:
Angular 2 HTTP Progress bar
After studying rxjs and reading Angular2 source code, I came up with this solution
I found it's better to make custom_backend, I think this is the recommended approach by angular Dev team.
my_backend.ts
import {Injectable} from "angular2/core";
import {Observable} from "rxjs/Observable";
import {Observer} from "rxjs/Observer";
import {Connection,ConnectionBackend} from "angular2/src/http/interfaces";
import {ReadyState, RequestMethod, ResponseType} from "angular2/src/http/enums";
import {ResponseOptions} from "angular2/src/http/base_response_options";
import {Request} from "angular2/src/http/static_request";
import {Response} from "angular2/src/http/static_response";
import {BrowserXhr} from "angular2/src/http/backends/browser_xhr";
import {Headers} from 'angular2/src/http/headers';
import {isPresent} from 'angular2/src/facade/lang';
import {getResponseURL, isSuccess} from "angular2/src/http/http_utils"
export class MyConnection implements Connection {
readyState: ReadyState;
request: Request;
response: Observable<Response>;
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
this.request = req;
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
let _xhr: XMLHttpRequest = browserXHR.build();
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
// save the responses in array
var buffer :string[] = [];
// load event handler
let onLoad = () => {
let body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText;
//_xhr.respons 1 = "Loading data!"
//_xhr.respons 2 = "Loading data!Ready To Receive Orders."
// we need to fix this proble
// check if the current response text contains the previous then subtract
// NOTE: I think there is better approach to solve this problem.
buffer.push(body);
if(buffer.length>1){
body = buffer[buffer.length-1].replace(buffer[buffer.length-2],'');
}
let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
let url = getResponseURL(_xhr);
let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
let state:number = _xhr.readyState;
if (status === 0) {
status = body ? 200 : 0;
}
var responseOptions = new ResponseOptions({ body, status, headers, url });
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
let response = new Response(responseOptions);
//check for the state if not 4 then don't complete the observer
if(state !== 4){
//this will return stream of responses
responseObserver.next(response);
return;
}
else{
responseObserver.complete();
return;
}
responseObserver.error(response);
};
// error event handler
let onError = (err: any) => {
var responseOptions = new ResponseOptions({ body: err, type: ResponseType.Error });
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
};
if (isPresent(req.headers)) {
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
}
_xhr.addEventListener('progress', onLoad);
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
_xhr.send(this.request.text());
return () => {
_xhr.removeEventListener('progress', onLoad);
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
};
});
}
}
#Injectable()
export class MyBackend implements ConnectionBackend {
constructor(private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions) {}
createConnection(request: Request):MyConnection {
return new MyConnection(request, this._browserXHR, this._baseResponseOptions);
}
}
In the main component we have to provide the custom bakend like this:
providers: [
HTTP_PROVIDERS,
PostSrevice,
MyBackend,
provide(XHRBackend, {useExisting:MyBackend})
]
Now when we use http.get() it will return a stream of Observable

Categories