I am creating a chat page in my ionic application and I would like to know when the get request changes. I am also OK with doing this all off of an interval, but I can’t find a good example on how to do it. I would prefer to not have to have the user refresh the page to see if a new chat message has appeared. I am newer to observables and have not seen this done with the remote data source and the HTTP package. My data is already JSON so that's why it's not mentioned at all. Thanks!
Chat.ts
import { Component, ViewChild ,ElementRef} from '#angular/core';
import { IonicPage, NavController, NavParams, Events, Content } from 'ionic-angular';
import { ChatProvider } from "../../../../../providers/chat/chat";
import { Observable } from "rxjs/Observable";
export class ChatShowPage {
public id:string;
public items: Observable<ChatMessage[]> = [];
constructor(navParams: NavParams, private chatService: ChatProvider, private events: Events,) {
this.id = navParams.get('id_convo');
}
getMsg() {
return this.chatService.getMsgList(this.id).subscribe(res => {
this.items=res;
console.log(this.items);
});
}
}
Provider.ts
getMsgList(id): Observable<ChatMessage[]> {
const msgListUrl = this.base_url + 'user/account/chat/show/' + id;
console.log('getMsgList - ' + msgListUrl);
return this.http.get<any>(msgListUrl,
{headers: new HttpHeaders({'Content': 'application/json','Accept': 'application/json','Authorization': 'Bearer ' + localStorage.getItem('tk') })});
}
You want to use Events to update your component
In this particular example, you could use:
ionViewWillLeave() {
// unsubscribe
this.events.unsubscribe('chat:received');
}
ionViewDidEnter() {
//get message list
this.getMsg();
// Subscribe to received new message events
this.events.subscribe('chat:received', msg => {
this.pushNewMsg(msg);
})
}
In your provider.ts your getMsgList function should emit the chat:received event as such:
this.events.publish('chat:received')
Then in your pushNewMsg function, you would do some safety checks and then push the new message into the array which you can display with an *ngFor directive.
There are two famous ways to make chat module via web(including ionic).
Polling
WebSocket (Event-driven)
Polling
: If you make simple or personal chat application and want to keep current code structure, then you'd better use polling pattern which merely repeat request with some interval. In addition, for better polling, please consider HTTP Long Polling
setTimeout() with recursion or setInterval() can be options.
WebSocket (Event-driven)
: If you make commercial application or application with lots of users, then you have to use Event-driven pattern. For using this pattern, both of server-side work and client-side work are needed. If you use node.js as server-side framework, then I recommend to use Socket.IO.
Related
I am sorry if it is obvious/well-covered elsewhere, but my google-fu has been failing me for over a full day by now. What I would like to achieve is a rich component-level handling of request errors: toaster notifications, status bars, you name it. The most obvious use case is auth guards/redirects, but there may be other scenarios as well (e.g. handling 500 status codes). For now, app-wide interceptors would do, but there is an obvious (to me, at least) benefit in being able to supplement or override higher-level interceptors. For example, if I have interceptors for 403 and 500 codes app-wide, I might want to override an interceptor for 403, but leave an interceptor for 500 intact on a component level.
This would require access to component properties: I could then pass status messages in child components, create toaster notifications with custom timeouts/animations and so on. Naively, for my current app-wide problem, this functionality belongs in App.vue, but I can not figure out how to get access to App in axios.interceptors.response using the current plugin arrangement and whether it is okay to use a single axios instance app-wide in the first place.
The trimmed down code I have tried so far (and which seems the most ubiquitous implementation found online) can be found below. It works with redirects, producing Error: getTranslators: detection is already running in the process (maybe because another 401 happens right after redirect with my current testing setup). However, import Vue, both with curly brackets and without, fails miserably, and, more importantly, I have no way of accessing app properties and child components from the plugin.
// ./plugins/axios.js
import axios from 'axios';
import { globalStorage } from '#/store.js';
import router from '../router';
// Uncommenting this import gives Uncaught SyntaxError: ambiguous indirect export: default.
// Circular dependency?..
// import Vue from 'vue';
const api = axios.create({
baseURL: import.meta.env.VUE_APP_API_URL,
});
api.interceptors.response.use(response => response,
error => {
if (error.response.status === 401) {
//Vue.$toast("Your session has expired. You will be redirected shortly");
delete globalStorage.userInfo;
localStorage.setItem('after_login', router.currentRoute.value.name);
localStorage.removeItem('user_info');
router.push({ name: 'login' });
}
return Promise.reject(error);
});
export default api;
// --------------------------------------------------------------------------
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import axios from './plugins/axios'
import VueAxios from 'vue-axios'
const app = createApp(App)
app.use(router)
.use(VueAxios, axios)
.mount('#app')
So, then, how do I get access to component properties in interceptors? If I need them to behave differently for different components, would I then need multiple axios instances (assuming the behavior is not achieved by pure composition)? If so, where to put the relevant interceptor configuration and how to ensure some parts of global configuration such as baseURL apply to all of these instances?
I would prefer not having more major external dependencies such as Vuex as a complete replacement for the existing solution, but this is not a hill to die on, of course.
Instead of using axios's interceptors, you should probably create a composable. Consider the following:
composables/useApiRequest.js
import axios from 'axios';
import { useToast } from "vue-toastification";
const useApiRequest = () => {
const toast = useToast();
const fetch = async (url) => {
try {
await axios.get(url);
} catch (error) {
if (error.response.status === 403) {
toast.error("Your session has expired", {
timeout: 2000
});
}
}
};
return {
fetch,
};
};
export default useApiRequest;
Here we're creating a composable called useApiRequest that serves as our layer for the axios package where we can construct our api requests and create generic behaviors for certain response attributes. Take note that we can safely use Vue's Composition API functions and also components such as the vue-toastification directly in this composable:
if (error.response.status === 403) {
toast.error("Your session has expired", {
timeout: 2000
});
}
We can import this composable in the component and use the fetch function to send a GET request to whatever url that we supply:
<script setup>
import { ref } from 'vue';
import useApiRequest from '../composables/useApiRequest';
const searchBar = ref('');
const request = useApiRequest();
const retrieveResult = async () => {
await request.fetch(`https://api.ebird.org/v2/data/obs/${searchBar.value}/recent`);
}
</script>
And that's it! You can check the example here.
Now, you mentioned that you want to access component properties. You can accomplish this by letting your composable accept arguments containing the component properties:
// `props` is our component props
const useApiRequest = (props) => {
// add whatever logic you plan to implement for the props
}
<script setup>
import { ref } from 'vue';
import useApiRequest from '../composables/useApiRequest';
import { DEFAULT_STATUS } from '../constants';
const status = ref(DEFAULT_STATUS);
const request = useApiRequest({ status });
</script>
Just try to experiment and think of ways to make the composable more reusable for other components.
Note
I've updated the answer to change "hook" to "composable" as this is the correct term.
I'm using the cordova in-app-browser plugin. One Page I get back is just a bunch of JSON-Data which i want to store inside my IONIC 5 Project. I could'nt figure out yet how to receive the Data and transfer it to the App yet with the Plugin. Is there such a possibility?
Thank you
To transfer data using InAppBrowser you can pass It by parameters, and also to receive the date you get it from parameters. Follows a small example:
Short way sending data on Page1.ts:
const dataToSend = `/${this.dataOne}/${this.dataTwo}`;
let finalUrl = `https://app-example.io` + dataToSend;
this.inAppB.create(finalUrl, '_system');
Receiving data on Page2.ts:
import { ActivatedRoute } from '#angular/router';
constructor(
private actRoute: ActivatedRoute
){}
ngOnInit() {
this.actRoute.paramMap.subscribe( params => {
console.log('Params => ', params);
if (params) {
let dataReceived = [params['params']['dataOne'], params['params']['dataTwo']];
console.log('dataReceived => ', dataReceived);
}
}
}
Please, adapt it to your code and variables as it is just a short example.
I have following case, I have a client implemented in vue.js and a python base backend using flask framework.
I have a restful interface where from client I need to send a request to server to start certain operations. This operation may take long time 4-5 minutes and I know to show the progress. How this can be implemented between client and server with current technology stack for a http REST interface.
Since there are buncha of "loading" components for Vue (as Nuxt has their own $loading), i will just use an example b-spinner of BootstrapVue and Axios as HTTP Client:
<b-spinner ng-if="loadingProgress"> (shows Spinner if loadingProgress/sending request until server return response)
methods: {
sendEmail() {
//set loading progess as true
this.loadingProgress = true;
axios.post(
'[your_API_URL]',
{
// data you wanted to POST as JSON
},
).then((response) => {
// reset your component inputs like textInput to null
// or your custom route redirect with vue-router
}).catch((error) => {
if (error.response) {
alert(error.response.data); // the error response
}
});
},
Further example of my contact form using BSpinner + AJAX Axios POST on Formspree.io here
Another is vue-wait component (link here)
Hope those're what you're seeking.
Should it be implemented in the action creator, or in a service class or component? Does the recommendation change if it's an isomorphic web app?
I've seen two different examples:
Action creator dispatches an action login_success/login_failure after making the rest call
Component calls an api service first and that service creates a login_success or failure action directly
example 1
https://github.com/schempy/react-flux-api-calls
/actions/LoginActions.js
The action itself triggers a call to the api then dispatches success or failure
var LoginActions = {
authenticate: function () {
RESTApi
.get('/api/login')
.then(function (user) {
AppDispatcher.dispatch({
actionType: "login_success",
user: user
});
})
.catch(function(err) {
AppDispatcher.dispatch({actionType:"login_failure"});
});
};
};
example 2
https://github.com/auth0/react-flux-jwt-authentication-sample
The component onclick calls an authservice function which then creates an action after it gets back the authentication results
/services/AuthService.js
class AuthService {
login(username, password) {
return this.handleAuth(when(request({
url: LOGIN_URL,
method: 'POST',
crossOrigin: true,
type: 'json',
data: {
username, password
}
})));
}
logout() {
LoginActions.logoutUser();
}
signup(username, password, extra) {
return this.handleAuth(when(request({
url: SIGNUP_URL,
method: 'POST',
crossOrigin: true,
type: 'json',
data: {
username, password, extra
}
})));
}
handleAuth(loginPromise) {
return loginPromise
.then(function(response) {
var jwt = response.id_token;
LoginActions.loginUser(jwt);
return true;
});
}
}
What's the better/standard place for this call to live in a Flux architecture?
I use an api.store with an api utility. From https://github.com/calitek/ReactPatterns React.14/ReFluxSuperAgent.
import Reflux from 'reflux';
import Actions from './Actions';
import ApiFct from './../utils/api.js';
let ApiStoreObject = {
newData: {
"React version": "0.14",
"Project": "ReFluxSuperAgent",
"currentDateTime": new Date().toLocaleString()
},
listenables: Actions,
apiInit() { ApiFct.setData(this.newData); },
apiInitDone() { ApiFct.getData(); },
apiSetData(data) { ApiFct.setData(data); }
}
const ApiStore = Reflux.createStore(ApiStoreObject);
export default ApiStore;
import request from 'superagent';
import Actions from '../flux/Actions';
let uri = 'http://localhost:3500';
module.exports = {
getData() { request.get(uri + '/routes/getData').end((err, res) => { this.gotData(res.body); }); },
gotData(data) { Actions.gotData1(data); Actions.gotData2(data); Actions.gotData3(data); },
setData(data) { request.post('/routes/setData').send(data).end((err, res) => { Actions.apiInitDone(); }) },
};
In my experience it is better to use option 1:
Putting API calls in an action creator instead of component lets you better separate concerns: your component(-tree) only calls a "log me in" action, and can remain ignorant about where the response comes from. Could in theory come from the store if login details are already known.
Calls to the API are more centralized in the action, and therefore more easily debugged.
Option 2 looks like it still fits with the flux design principles.
There are also advocates of a third alternative: call the webAPI from the store. This makes close coupling of data structures on server and client side easier/ more compartmental. And may work better if syncing independent data structures between client and server is a key concern. My experiences have not been positive with third option: having stores (indirectly) create actions breaks the unidirectional flux pattern. Benefits for me never outweighed the extra troubles in debugging. But your results may vary.
What's the best way to implement long-polling for user-specific events? I have a "home feed" for each user that I want dynamically updated with new information immediately as it comes in.
Right now I'm making an AJAX call from the client to /register?id=2, for example, to listen for updates. The server itself sends a GET request to /emit?id=2&eventid=3 when some other event(eventid=3) related to user(id=2) occurs. The Node.js server has code similar to below:
var event_emitter = new events.EventEmitter();
http.createServer(function(req,res) {
var uriParse = url.parse(req.url,true);
var pathname = uriParse.pathname;
if (pathname=="/register") {
var userid = uriParse.query.id;
var thisRes = res;
event_emitter.addListener('event'+userid,function(eventid){
if (thisRes) {
console.log(thisRes);
thisRes.writeHead(200, { "Content-Type": "text/plain" });
thisRes.end(eventid);
thisRes = null;
}
});
} else if (pathname=="/emit") {
var userid = uriParse.query.id;
var eventid = uriParse.query.eventid;
event_emitter.emit('event'+userid,eventid);
res.writeHead(200, { "Content-Type": "text/plain" });
res.end('success');
}
}).listen(3000,"0.0.0.0");
So the client's AJAX call doesn't load until the event occurs, at which point the server returns a response with the eventid. The client uses this eventid to make a different AJAX call (unrelated, since I'm using Django for the main application but Node.js for event-driven super-powers).
I'm just bothered by creating so many listeners for "event2" "event454" etc as more users connect. Is this scalable with something like Node.js? How else can I emit user-specific events?
Thanks in advance!
You can remove the listener after emitting the events using removeAllListeners.