As said in the title, I'm using Django, GraphQL, Apollo and VueJS in my project.
I'm developping it as a SPA (Single Page Application).
Everything works fine, until I hit the F5 button and refresh the page. Indeed, it shows an unknown page. The thing is it is VueRouter that is managing the SPA and it works fine. But when I press F5, that is Django that tries to serve a page for the current URL and since it doesn't know it, it can't serve the appropriate page.
I know I can set the VueRouter 'history' mode, which I did, and add a URL to Django that serves index.html whatever the URL is.
My problem is the following :
When I'm on a particular form view (i.e : a User form view) my URL is the following :
http://localhost:8000/user
Since I'm using GraphQL for my API, the retrieved data is not based on the URL. In fact, that is my VueJS component that says : Hey Apollo, run that GraphQL to retrieve the User I want.
So when I refresh, yes it serves the User form view..but empty.
The question is : How could I solve this ?
For clarification purposes, here are some code samples :
My Django URLs :
# (... other imports here ...)
from .schema import schema
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql', csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))), # 'schema' is the main GraphQL schema
path('', TemplateView.as_view(template_name='index.html')),
re_path(r'^.*$', TemplateView.as_view(template_name='index.html')) # I saw that many times to serve the page whatever the URL is when refreshing the page
]
My Vue Router :
export default new Router({
mode: 'history',
routes: [
{ path: '/', name: 'MainApp' },
// ...
{ path: '/users', name: 'UserList', component: UserList },
{ path: '/user/create', name: 'UserFormCreate', component: UserForm, props: true },
{ path: '/user', name: 'UserFormView', component: UserForm, props: true },
{ path: '/user/edit', name: 'UserFormEdit', component: UserForm, props: true },
// Same pattern for other models like 'Group' ...
]
My Example VueJS Component :
<script>
import {
// ...
USER_QUERY,
// ...
} from '../../graphql/base/user.js'
export default {
name: 'UserForm',
props: {
userId: Number,
editing: {
type: Boolean,
default: false
}
},
apollo: {
user: {
query: USER_QUERY,
variables () { return { id: this.userId } },
skip () { return this.userId === undefined },
result ({ data }) {
this.form.username = data.user.username
this.form.firstName = data.user.firstName
this.form.lastName = data.user.lastName
}
}
},
data () {
return {
form: {
username: '',
password: '',
firstName: '',
lastName: ''
},
// ...
}
},
methods: {
// ...
}
I have to mention that I've seen more or less related topics but that doesn't solve my problem.
Thanks in advance for your help !
Edit your route paths to use params. For example:
{ path: '/user/:userId', name: 'UserFormView', component: UserForm, props: true }
Now, the app will interpret any number following the user/ path as a prop called userId. (props: true is important here for using the params as props.)
The only other change you need to make is adjusting your router-links to include the id as well (Ex.: http://localhost:8000/user/1) so that when the page is refreshed, there will be a param to read.
Related
I am using django authentication, I want to use angular router guards when not signed in. So that it reroutes to login page if not logged in.
I have tried to setup as angular usually would with router guards, but this routes to the url without a trailing slash which doesn't work with Django. I have fixed it so it keeps the trailing slash, but this doesn't route to the Django page, it seems its looking for a Angular page. But if it type in the url for the Django login page that still works.
Auth Guard:
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
this.authService.redirectUrl = url;
this.router.navigate(['/accounts/login/.']);
return false;
}
app-routing module:
{
path:"",
component: ProjectHomeComponent,
canActivate : [AuthGuard],
children: [
{
path: '',
children: [
{ path: 'view', component: ProjectViewComponent },
{ path: 'seeManage', component: ProjectManageComponent },
{ path: '**', component: PagenotfoundComponent }
]
}
]
}
Expect to be routed to django login page, not routed to django login page
I have a PWA built with ionic deep linker. I have done a demo here https://stackblitz.com/edit/ionic-mee2ut?file=app%2Fcustomer%2Fcustomer.component.html where the browser back button doesn't work as expected.
Steps to reproduce
1.In Dashboard page click on edit button.It will navigate to customer
page(see URL.It is changed to /Customer/CustomerId).
2.In Customer page, you will see the customer info and other customers
list, there click edit from other customers list.This will open another
page.(see URL.It is changed to /Customer/CustomerId).
3.Click on browser back button u can see that the URL is changed but the
view is not updated.
If I repeat steps 1 & 2 then click on nav back button instead of browser button then it works correctly.Both the URL and the view gets updated.
Is there something I am doing wrong because the browser back button does not work as expected or this is issue of ionic framework.
This is how i navigate between views
EditCustomer(Customer: any) {
this.navCtrl.push('Customer', { Id: Customer.Id, Name: Customer.Name });
}
Can somebody please tell me a way how to resolve this issue?
I saw your code in the above url, you are passing id as param but not the name so, that is the reason url is changing but data is not reflected i modified your code in app.module.ts file please replace this code in your app.module.ts file
IonicModule.forRoot(MyApp, {}, {
links: [
{ component: DashboardComponent, name: 'Dashboard', segment: 'Dashboard' },
{ component: CustomerComponent, name: 'Customer', segment: 'Customer/:Id/:Name' }
]
})
Please replace your app.module.ts with the following code
import { Component } from '#angular/core';
import { Platform, IonicApp, App } from 'ionic-angular';
#Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = 'Dashboard';
constructor(private _app: App, platform: Platform, private _ionicApp: IonicApp,) {
platform.ready().then(() => {
this.setupBackButtonBehavior();
});
}
private setupBackButtonBehavior () {
// If on web version (browser)
if (window.location.protocol !== "file:") {
// Register browser back button action(s)
window.onpopstate = (evt) => {
//Navigate back
if (this._app.getRootNav().canGoBack())
this._app.getRootNav().pop();
};
}
}
}
I was able to use something like this:
let randomID = this.makeId(5); // random string id
this.navCtrl.push('path', {
eventID: eventID,
instituteID: instituteID,
randomID: randomID
}, {
id: `path/${eventID}/${instituteID}/${randomID}`
});
This "id" seems to fix it, but if you can go to the same page, then it requires a "random" value to separate each visit to that page.
#IonicPage({
name: 'path',
segment: 'path/:instituteID/:eventID/:randomID'
})
It looks like, by default, it uses the name of the page as an id for that view. If multiple views have same id => issue when using browser back/forward. That's where the random comes in, to separate multiple instances of the same page.
I am trying to implement navigation in Ionic 2. I have tried with DeepLinking and i got the result, but '#' sign is comming in URL.
When '#' sign will come in URL then Google Analytic will not recognize the website, that's why i have tried to implement navigation in different ways like Angular 2 Routing, that supports both (HTML5 or hash URL style), but unable to implement in Ionic 2.
Ex- http://localhost:8100/#/registration - This one working fine but i want without '#'.
Like http://localhost:8100/registration
Thanks for help
I put in a PR for #ionic/app-scripts 3.2.5 to remedy this:
https://github.com/ionic-team/ionic-app-scripts/pull/1545
In the meantime you can edit some project and dependency files to enable it:
src/app/app.module.ts:
IonicModule.forRoot(MyApp,
{
locationStrategy: 'path'
},
{
links: [
{ component: RegistrationPage, name: 'registration', segment: 'registration' },
{ component: LoginPage, name: 'login', segment: 'login' },
{ component: HomePage, name: 'home', segment: '' }
]
})
src/index.html:
<head>
...
<base href="/" />
...
</head>
node_modules/#ionic/app-scripts/dist/dev-server/http-server.js:
function createHttpServer(config) {
var app = express();
app.set('serveConfig', config);
app.get('/', serveIndex);
app.use('/', express.static(config.wwwDir));
app.use("/" + serve_config_1.LOGGER_DIR, express.static(path.join(__dirname, '..', '..', 'bin'), { maxAge: 31536000 }));
// Lab routes
app.use(serve_config_1.IONIC_LAB_URL + '/static', express.static(path.join(__dirname, '..', '..', 'lab', 'static')));
app.get(serve_config_1.IONIC_LAB_URL, lab_1.LabAppView);
app.get(serve_config_1.IONIC_LAB_URL + '/api/v1/cordova', lab_1.ApiCordovaProject);
app.get(serve_config_1.IONIC_LAB_URL + '/api/v1/app-config', lab_1.ApiPackageJson);
app.get('/cordova.js', servePlatformResource, serveMockCordovaJS);
app.get('/cordova_plugins.js', servePlatformResource);
app.get('/plugins/*', servePlatformResource);
if (config.useProxy) {
setupProxies(app);
}
app.all('/*', serveIndex);
return app;
}
The line app.all('/*', serveIndex); is what will redirect any 404 file or directory not found errors to index.html. The locationStrategy: 'path' setting can then work normally with deeplinks and redirects under these circumstances.
Try to use pathLocationStrategy instead of HashLocationStrategy.
Add this in app.module.ts
import { LocationStrategy,
PathLocationStrategy } from '#angular/common';
...
#NgModule({
...
providers: [
{
provide: LocationStrategy,
useClass: PathLocationStrategy
},
...
Or other way is
IonicModule.forRoot(MyApp, {
locationStrategy: 'path'
})
And make sure to have a valid base href.
So here is the list of things which I did. Hope this helps.
We need to remove # in path of every url because Google Analytics rejects the urls with # in them. In App Module , add {locationStrategy: 'path'} to your App Module as follows :
IonicModule.forRoot(MyApp, {
locationStrategy: 'path'
})
2 .Now # is removed from the url. But when you refresh or directly access the url, this wont work because this is expected behaviour for any SPA. When you refresh the page , server tried to find the page at the location mentioned. As stated by #Parth Ghiya above For eg: if you hit localhost/abc , then server tries to find abc/index.html which actually doesn't exist.So to resolve this , you have wrote configurations on my server i.e to point every request to index.html . I am using node express server to deploy the app. Use the following code to route every request to index.html -
var express = require('express');
var path = require('path')
var app = express();
app.use(express.static(path.resolve(__dirname, "www")));
app.use('/*', function(req, res){
res.sendFile(__dirname+ '/www' + '/index.html');
});
app.set('port', process.env.PORT || 3000);
app.listen(app.get('port'), function() {
console.log("listening to Port", app.get("port"));
});
I am working on my first Ember app and got it to display the way I wanted with the route returning a static JSON object from model():
element: {
name: "First Element",
divisions: [{
name: "First Division",
sets: [{name: "Set 1"},{name: "Set 2"},{name: "Set 3"}]
}, {
name: "Second Division",
sets: [{name: "Set 1"},{name: "Set 2"},{name: "Set 3"}]
}]
}
Now I am trying to refactor to use Ember Data + Mirage and having an awful time.
Here’s my index.js route
export default Ember.Route.extend({
model() {
return this.store.find('element', 1);
},
If I set up my Mirage config.js like this:
this.get('/elements', function() {
return {
elements: [
{
id: 1,
name: 'First Element',
divisions: [1, 2]
}
]
}
});
then I get this error:
Your Ember app tried to GET '/elements/1', but there was no route defined to handle this request.
If I set up my Mirage config.js like this:
this.get('/elements/1', function() {
return {
id: 1,
name: 'First Element',
divisions: [1, 2]
}
});
then I get this error:
22:46:40.883 "Error while processing route: index" "Assertion Failed: normalizeResponse must return a valid JSON API document:
* One or more of the following keys must be present: "data", "errors", "meta"." "EmberError#http://localhost:4200/assets/vendor.js:25582:15
EDIT:
So this isn't a solution to the problem as stated but it got me past this. I gave up on Pretender and started again creating an actual Rails server according to this excellent tutorial: http://emberigniter.com/modern-bridge-ember-and-rails-5-with-json-api/
I was able to do everything I wanted this way and if I ever want to make this a production app, I'm a lot closer.
So the issue is that you aren't actually adhering to the JSON API specification. You can solve this by reading Mirage's page on how to conform.
Essentially you need to either be returning an object at the top level of your JSON response in the case of a GET /foo/1 call. You'll also need to change your "elements" attribute to "data" for GET /foo and that should do the trick. Right now there isn't a simple, re-usable way to do this Mirage out of the box. The best bet right now for both issues is to use the solution presented in this issue.
ember error normalizeResponse must return a valid JSON API document
can be fixed in three ways
return a valid JSONAPI response
see your error message:
normalizeResponse must return a valid JSON API document:
* One or more of the following keys must be present: "data", "errors", "meta".
this.get('/elements/1', function() {
return {
data: {
id: 1,
name: 'First Element',
divisions: [1, 2]
}
}
});
see also https://jsonapi.org/examples/
normalize all responses
// app/serializers/application.js
import EmberData from "ember-data";
export default EmberData.JSONAPISerializer.extend({
normalizeResponse() {
return {
data: this._super(...arguments),
};
},
//normalize(){},
//serialize(){},
// ...
});
problem: error handling
by wrapping all responses in { data: ... }, they never return errors
on errors, the response should be
this.get('/elements/1', function() {
return {
errors: [
{
id: 12345,
title: 'title for error #12345'
}
]
}
});
see also https://jsonapi.org/format/#error-objects
replace JSONAPI with REST
sed -i 's/JSONAPISerializer/RESTSerializer/g' app/serializers/*.js
sed -i 's/JSONAPIAdapter/RESTAdapter/g' app/adapters/*.js
ember docs: adapters and serializers
duplicate: How can ember application be integrated to use with json-server?
I have a model record created and being saved through a route and controller. When I save the record through the controller (via a savePlace action), I am seeing this error in the JS console:
SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data
I've tried not setting anything on the model as well as setting dummy data on the model, but I get the same error. I am also user ember-cli http-mocks as a test backend to handle JSON responses. I realize it may be the response, but I'm not sure how else to configure the response.
Here's the relevant code:
routes/places/new.js:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('place');
},
});
controllers/places/new.js:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
saveGeom(geom) {
this.get('model').set('geometry', geom);
},
savePlace(data) {
this.get('model').set('name', this.get('name')).set('description', this.get('description'));
this.get('model').save().then(function() {
alert("SUCCESS");
}, function(error) {
console.log(error);
});
}
}
});
server/mocks/place.js:
placeRouter.post('/places', function(req, res) {
res.setHeader('Access-Control-Allow-Methods', 'POST');
res.send({
"places": {
id: 1,
name: "Triangle",
description: "Ryan Christiani",
geometry: {
"type": "Polygon",
"coordinates": [
[
[-84.32281494140625,34.9895035675793],
[-81.73690795898438,36.41354670392876],
[-83.616943359375, 34.99850370014629],
[-84.05639648437499,34.985003130171066],
[-84.22119140625, 34.985003130171066],
[-84.32281494140625,34.9895035675793]
]
]
}
}
});
});
Thanks!
I think you are using the wrong brackets in the wrong places in your JSON Object.
Check out this page
http://www.tutorialspoint.com/json/json_syntax.htm
The http-mocks configuration is wrong. It should be this the code snippet below. The server was instead responded with an array of objects (the response for 'GET /'). Not sure why that would trigger a JSON.parse error, but this is the correct configuration.
placeRouter.post('/', function(req, res) {
res.setHeader('Access-Control-Allow-Methods', 'POST');
res.send({
'places': [
{
id: 1,
name: "Using Ember CLI to create a Fixture Adapter.",
description: "Ryan Christiani",
geometry: {
"type": "Polygon",
"coordinates": [
[
[-84.32281494140625,34.9895035675793],
[-81.73690795898438,36.41354670392876],
[-83.616943359375, 34.99850370014629],
[-84.05639648437499,34.985003130171066],
[-84.22119140625, 34.985003130171066],
[-84.32281494140625,34.9895035675793]
]
]
}
}]});
});