Trying to access Django REST API in Angular2 - django

I am working on a project where we use Angular2 and a Django backend that has a rest API. Right now we are simply just trying to get the django backend to send JSON objects to Angular2. For some reason when we make get requests, it comes back as blank. Right now we just have dummy test functions, but even those don't work.
/_services/user.service.ts
tempFunc() {
return this.http.get('http://localhost:8000/chains/', this.jwt()).map((model: Response) => model.json());
}
/temptemp-page.component.ts
import { Router } from '#angular/router';
import { UserService } from '../_services/user.service';
export class Temp {
name : string;
description : string;
slogan : string;
founded_date : string;
website : string;
}
#Component({
selector: 'app-temp-page',
templateUrl: './temp-page.component.html',
styleUrls: ['./temp-page.component.css']
})
export class TempPageComponent implements OnInit {
model: Temp;
constructor(
private router: Router,
private userService: UserService) { }
ngOnInit() {
this.model = {
name: 'Wrong',
description: 'Wrong',
slogan: 'Wrong',
founded_date: 'Wrong',
website: 'Wrong',
}
this.userService.tempFunc().subscribe(model => this.model = model);
console.log(this.model);
}
}
The Wrong is there just to know that if we get nothing, it will print Wrong and we know the get request isn't succeeding.
temp-page-componenent.html
<div style="margin-left: 20px;">
name: {{ this.model.name }} <br/>
description: {{ this.model.description }} <br/>
slogan: {{ this.model.slogan }} <br/>
founded_date: {{ this.model.founded_date }} <br/>
website: {{ this.model.website }} <br/>
<hr/>
</div>
The django backend has a model with field of the type above in this html file, in a table called Chains. at that URL specified. For some reason, every attempt to call it works. Except for Angular2, and I am asking this to figure out if there is just bad syntax, or something else related to the problem. I know it works because when I do
curl -g localhost:8000/chains/
It works fine and returns
[{"name":"Cafe Amazing","description":"Founded to serve the best sandwiches.","slogan":"The best cafe in the Mississippi!","founded_date":"2014-12-04T20:55:17Z","website":"http://www.thecafeamazing.com"}]
with a sucess code on the django server of 200 204.
However when I try the angular2 code above it returns the same codes but nothing gets displayed. What am I doing wrong here?

don't use this in your template use only the property name, like this :
<div style="margin-left: 20px;">
name: {{ model.name }} <br/>
description: {{ model.description }} <br/>
slogan: {{ model.slogan }} <br/>
founded_date: {{ model.founded_date }} <br/>
website: {{ model.website }} <br/>
<hr/>
</div>
you tempFunc is asynchronous so console.log(this.model) will be executed first :
this.userService.tempFunc().subscribe(model => this.model = model);
console.log(this.model);
do this :
this.userService
.tempFunc()
.subscribe(model =>{
this.model = model
console.log(this.model);
});

Related

Ember Octane How to Get Error Messages to be Displayed?

This question is related to Ember Octane Upgrade How to pass values from component to controller
How do I get Ember Octane to display on the webpage? For instance, if the old password and new password are the same we want that error to display on the page.
Ember-Twiddle here
Code example:
User Input Form
ChangePasswordForm.hbs
<div class="middle-box text-center loginscreen animated fadeInDown">
<div>
<h3>Change Password</h3>
<form class="m-t" role="form" {{on "submit" this.changePassword}}>
{{#each this.errors as |error|}}
<div class="error-alert">{{error.detail}}</div>
{{/each}}
<div class="form-group">
<Input #type="password" class="form-control" placeholder="Old Password" #value={{this.oldPassword}} required="true" />
</div>
<div class="form-group">
<Input #type="password" class="form-control" placeholder="New Password" #value={{this.newPassword}} required="true" />
</div>
<div class="form-group">
<Input #type="password" class="form-control" placeholder="Confirm Password" #value={{this.confirmPassword}} required="true" />
</div>
<div>
<button type="submit" class="btn btn-primary block full-width m-b">Submit</button>
</div>
</form>
</div>
</div>
Template Component
ChangePassword.hbs
<Clients::ChangePasswordForm #chgpwd={{this.model}} #changePassword={{action 'changePassword'}} #errors={{this.errors}} />
Component
ChangePasswordForm.js
import Component from '#glimmer/component';
import { tracked } from '#glimmer/tracking';
import { action } from '#ember/object';
export default class ChangePasswordForm extends Component {
#tracked oldPassword;
#tracked newPassword;
#tracked confirmPassword;
#tracked errors = [];
#action
changeOldPassword(ev) {
this.oldPassword = ev.target.value;
}
#action
changeNewPassword(ev) {
this.newPassword = ev.target.value;
}
#action
changeConfirmPassword(ev) {
this.confirmPassword = ev.target.value;
}
#action
changePassword(ev) {
ev.preventDefault();
this.args.changePassword({
oldPassword: this.oldPassword,
newPassword: this.newPassword,
confirmPassword: this.confirmPassword
});
}
}
Controller
ChangePassword.js
import Controller from '#ember/controller';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default class ChangePassword extends Controller {
#service ajax
#service session
#action
changePassword(attrs) {
if(attrs.newPassword == attrs.oldPassword)
{
shown in the UI.
this.set('errors', [{
detail: "The old password and new password are the same. The password was not changed.",
status: 1003,
title: 'Change Password Failed'
}]);
}
else if(attrs.newPassword != attrs.confirmPassword)
{
this.set('errors', [{
detail: "The new password and confirm password must be the same value. The password was not changed.",
status: 1003,
title: 'Change Password Failed'
}]);
}
else
{
let token = this.get('session.data.authenticated.token');
this.ajax.request(this.store.adapterFor('application').get('host') + "/clients/change-password", {
method: 'POST',
data: JSON.stringify({
data: {
attributes: {
"old-password" : attrs.oldPassword,
"new-password" : attrs.newPassword,
"confirm-password" : attrs.confirmPassword
},
type: 'change-passwords'
}
}),
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/vnd.api+json',
'Accept': 'application/vnd.api+json'
}
})
.then(() => {
this.transitionToRoute('clients.change-password-success');
})
.catch((ex) => {
this.set('errors', ex.payload.errors);
});
}
}
}
Model
ChangePassword.js
import Route from '#ember/routing/route';
import AbcAuthenticatedRouteMixin from '../../mixins/efa-authenticated-route-mixin';
export default class ChangePasswordRoute extends Route.extend(AbcAuthenticatedRouteMixin) {
model() {
// Return a new model.
return {
oldPassword: '',
newPassword: '',
confirmPassword: ''
};
}
}
In your form component, you reference the errors like
{{#each this.errors as |error|}}
<div class="error-alert">{{error.detail}}</div>
{{/each}}
From class components -> glimmer components, there's been a fundamental shift in the way you access the component's arguments vs the component's own values (for the better!)
In class components, arguments are assigned directly to the class
instance. This has caused a lot of issues over the years, from methods
and actions being overwritten, to unclear code where the difference
between internal class values and arguments is hard to reason about.
New components solve this by placing all arguments in an object
available as the args property.
When referencing an argument to a component in javascript, you use: this.args.someArg. In the template, you use the shorthand #someArg. These are known as "named arguments" (feel free to read the rfc for more info). When you, as you did here, use this.errors in your template, you are looking for a local component property errors.
Just to emphasize, this does not work because errors is passed to Clients::ChangePasswordForm via #errors here:
<Clients::ChangePasswordForm #chgpwd={{this.model}} #changePassword={{action 'changePassword'}} #errors={{this.errors}} />
and must be #errors in the template
{{#each #errors as |error|}}
<div class="error-alert">{{error.detail}}</div>
{{/each}}

Nuxt JS Apollo data only available after page refresh

I am fetching some data using Apollo inside of Nuxt. Somehow, when navigating to that page I get an error of
Cannot read property 'image' of undefined
When I refresh the page, everything works as expected.
I have a found a few threads of people having similar issues but no solution seems to work for me :/
This is my template file right now:
/products/_slug.vue
<template>
<section class="container">
<div class="top">
<img :src="product.image.url"/>
<h1>{{ product.name }}</h1>
</div>
</section>
</template>
<script>
import gql from 'graphql-tag'
export default {
apollo: {
product: {
query: gql`
query Product($slug: String!) {
product(filter: { slug: { eq: $slug } }) {
slug
name
image {
url
}
}
}
`,
prefetch({ route }) {
return {
slug: route.params.slug
}
},
variables() {
return {
slug: this.$route.params.slug
}
}
}
}
}
</script>
Basically the $apolloData stays empty unless I refresh the page. Any ideas would be much appreciated
EDIT
Got one step closer (I think). Before, everything (image.url and name) would be undefined when navigating to the page for the first time.
I added:
data() {
return {
product: []
};
}
at the top of my export and now at least the name is always defined so if I remove the image, everything works as expected. Just the image.url keeps being undefined.
One thing I noticed (not sure how relevant) is that this issue only occurs using the , if I use a normal a tag it works but of course takes away the vue magic.
EDIT-2
So somehow if I downgrade Nuxt to version 1.0.0 everything works fine
I stumbled on this issue as well, and found it hidden in the Vue Apollo documents.
Although quite similar to the OP's reply, it appears the official way is to use the "$loadingKey" property.
It's quite confusing in the documents because there are so many things going on.
https://vue-apollo.netlify.com/guide/apollo/queries.html#loading-state
<template>
<main
v-if="!loading"
class="my-8 mb-4"
>
<div class="w-3/4 mx-auto mb-16">
<h2 class="mx-auto text-4xl text-center heading-underline">
{{ page.title }}
</h2>
<div
class="content"
v-html="page.content.html"
></div>
</div>
</main>
</template>
<script>
import { page } from "~/graphql/page";
export default {
name: 'AboutPage',
data: () => ({
loading: 0
}),
apollo: {
$loadingKey: 'loading',
page: {
query: page,
variables: {
slug: "about"
}
},
}
}
</script>
If you need to use a reactive property within vue such as a slug, you can do so with the following.
<template>
<main
v-if="!loading"
class="my-8 mb-4"
>
<div class="w-3/4 mx-auto mb-16">
<h2 class="mx-auto text-4xl text-center heading-underline">
{{ page.title }}
</h2>
<div
class="content"
v-html="page.content.html"
></div>
</div>
</main>
</template>
<script>
import { page } from "~/graphql/page";
export default {
name: 'AboutPage',
data: () => ({
loading: 0
}),
apollo: {
$loadingKey: 'loading',
page: {
query: page,
variables() {
return {
slug: this.$route.params.slug
}
}
},
}
}
</script>
I think it's only a problem of timing on page load.
You should either iterate on products, if you have more than one, or have a v-if="product != null" on a product container, that will render only once the data is fetched from GraphQL.
In that way you'll use the object in your HTML only when it's really fetched and avoid reading properties from undefined.
To fix this, you add v-if="!$apollo.loading" to the HTML container in which you're taying to use a reactive prop.

Vue Template not updating after data is changed

I am new to Vue, and I have played around with small vue applications, but this is the first project I've done using vue and webpack (vue-cli).
I have a django rest API set up, for which I then use vue to consume the API and display the data. The issue i'm having is when I want to do anything the templates aren't updating. The data is loaded in correctly, but if I try and do a #click event and then a v-show (to toggle displaying an item) it doesn't change the view.
Example,
lets say I have a list of pubs that I consume from the API.
I get them via axios, and store them in the data function in a array called pubs:
<script>
import axios from 'axios'
export default {
name: 'Pubs',
data () {
return {
pubs: [
{
pub_id:'',
name:'',
address:'',
showPub: false,
},
]
}
},
created: function () {
this.loadPubs();
},
methods: {
loadPubs: function () {
var vm = this;
axios.get('http://127.0.0.1:8000/api/pubs/')
.then(function (response) {
vm.pubs = response.data;
vm.pubs.forEach(function (pub) {
pub.showPub = true;
});
console.log("loaded");
})
.catch(function (error) {
this.pubs = 'An error occured.' + error;
});
},
togglePub: function (pub) {
pub.showPub = !pub.showPub;
console.log(pub.showPub);
console.log(pub);
return pub;
},
}
}
</script>
The template could be:
<template>
<div class="pubs">
<h1>VuePubs</h1>
<ul>
<li v-for="pub in pubs">
<section>
<h2 #click="togglePub(pub)">{{ pub.name }}</h2>
<section v-show="pub.showPub">
<h3>{{ pub.address }}</h3>
<h3>{{ pub.postcode }}</h3>
<h3>{{ pub.showPub }}</h3>
</section>
</section>
</li>
</ul>
</div>
</template>
I can see that the data is changing in the model, thanks to the vue-dev tools, but the template doesn't change. The section doesn't hide and the h3 tags don't update with the new showPub field.
Any help would be greatly appreciated!

Ember Data 2 - Submit Two Associated Models in One Form

Total Ember newb here. My back-end is written with Rails. That piece is fine, so assume all is well on the server side. Here are the specifics:
I have two models
Project (Has Many Project Details)
Project Details (Belongs To Project)
I have confirmed that:
I am able to create a new Project from my projects/new.hbs template
My Index route retrieves all Projects and the Project Details associated with them.
My Show route retrieves an individual Project and the Project Details associated with them.
What I would like to do is, submit a project model along with a project detail model from my projects/new.hbs template.
I understand that the form in new.hbs should be in a component but I am keeping it simple for now. Once, I am able to create my associated models I will work on doing the same as a form component.
Project Model
export default DS.Model.extend({
project_details: DS.hasMany('project_detail', { async: true }),
project_name: DS.attr('string'),
status: DS.attr('string')
});
Project Detail Model
export default DS.Model.extend({
project: DS.belongsTo('project', { async: true }),
project_id: DS.attr('number'),
feature_name: DS.attr('string'),
hours_billed: DS.attr('number'),
available_hours: DS.attr('number'),
amout: DS.attr('number')
});
Projects/New.js Route
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
projects: this.store.createRecord('project'),
project_details: this.store.createRecord('project_detail')
});
},
actions: {
create: function() {
var self = this;
this.controller.get('model').save().then(
function() {
self.transitionTo('projects.index');
});
}
}
});
Projects/New.hbs
<form {{ action "create" on="submit" }}>
<label for="project_name">Project Name</label>
{{ input value=project_name }}
<label for="status">Status</label>
{{ input value=status }}
<label for="feature_name">Feature Name</label>
{{ input value=feature_name }}
<label for="available_hours">Available Hours</label>
{{ input value=available_hours }}
<label for="hours_billed">Hours Billed</label>
{{ input value=hours_billed }}
<label for="amount">Amount</label>
{{ input value=amount }}
<button type="submit">Save</button>
</form>
Router.js
Router.map(function() {
this.route('home');
this.route('dashboard');
this.route('login');
this.resource('projects', function() {
this.route('new');
this.route('show', {path: '/:id'});
this.route('edit', {path: '/:id/edit'});
});
});
When I submit the form, the action appears to be called, but all data that is submitted is NULL, so evidently, my inputs are not being recognized. I have searched for a couple days and have not seen a solution for submitting multiple models. My code is a result of expanding on a standard POST along with how I handled retrieving my associated models via the RSVP Hash. Any advice would be great. Thanks!
You've got some things that are not wired up correctly.
When you return the following from your route:
model: function() {
return Ember.RSVP.hash({
projects: this.store.createRecord('project'),
project_details: this.store.createRecord('project_detail')
});
},
In your template, you can use {{model.something}} to access that information, in your case you have access to {{model.projects}} and {{model.project_details}}.
You probably want to do the following in your route to setup easier access to your models in the templates.
setupController(controller, model) {
//this._super(controller, model) I'll explain why this is commented
controller.set('project', model.projects);
controller.set('project_details', model.project_details);
}
If you do this, then in your template you can do things like this:
<label>Project Name</label>
{{input value=project.project_name}}
{{project.project_name}}
Which brings to the next thing. When you have in your template the following:
{{ input value=project_name }}
What you're doing is binding the input value to the project_name property of your controller. If you use the Ember Inspector (hint, if you're not using it, you should. It makes life so much easier) you can probably check that the controller has that property project_name with the value you typed.
Before Ember 2.0 there was this thing called the Object Controller (which you don't use anymore) that allowed proxying the model to the controller. Which meant that you could on your route do this:
model() {
return this.store.createRecord('project');
}
And in your template you effectively to this:
<label>Project Name</label>
{{project_name}}
Today you have to do this (Unless you install ember-legacy-controllers which I don't recommend):
<label>Project Name</label>
{{model.project_name}}
As you can see, you need to add the model before the project_name. This is because the model returned from the route is set as a property of the controller by default. That's something we can change, of course. You could also do the following in your route:
setupController(controller, model) {
controller.set('model', model.projects);
controller.set('myProjectDetails', model.project_details);
}
And in the template you could now do the following:
<form {{ action "create" on="submit" }}>
<label for="project_name">Project Name</label>
{{ input value=model.project_name }}
<label for="status">Status</label>
{{ input value=model.status }}
<label for="feature_name">Feature Name</label>
{{ input value=myProjectDetails.feature_name }}
<label for="available_hours">Available Hours</label>
{{ input value=myProjectDetails.available_hours }}
<label for="hours_billed">Hours Billed</label>
{{ input value=myProjectDetails.hours_billed }}
<label for="amount">Amount</label>
{{ input value=amount }}
<button type="submit">Save</button>
</form>
Which means that your save function now works because the "project" is now your model. If you used my example of setupController where I did this:
controller.set('project', model.projects);
Then you would have to change your create action to this:
actions: {
create: function() {
var self = this;
this.controller.get('project').save().then(
function() {
self.transitionTo('projects.index');
});
}
}
Also, do notice that you have your models declared with
//Project
project_details: DS.hasMany('project_detail', { async: true }),
//Project Detail
project: DS.belongsTo('project', { async: true }),
But you're not setting the relations in the code you posted. If you want the models to be related, in your create action you would need something like this (or in any other place really). I'm assuming in the following snippet that my setupController example is in the route so that the project is in the controller as project and the project_details as project_details.
create: function() {
var self = this;
var project = this.controller.get('project');
var projectDetails = this.controller.get('project_details');
project.get('project_details').pushObject(projectDetails);
project_details.set('project', project);
project.save().then(function() {
self.transitionTo('projects.index');
});
}
Assuming you're using Ember Data 2.0, there's also another problem with this. When you save the project it will only make a post request to save the project itself (it won't save the newly created project_details with it). At the moment JSONAPI(the standard Ember Data 2.0 by default uses) does not have a solution for updating multiple resources in one go (see this github issue). This means that you would have to save the project_details first and only then then project (which probably doesn't make sense looking at your models as it seems the project_details only exists under a project.)
It's the current state of affairs, as far as I know. I'm also looking for a good solution for this problem (I've hacked together solutions like only allowing to create a project_detail after the project is created, or having an extra attribute in the project with is a string of the serialized project_detail json and then have the backend to the things it needs)

ember-data stores a string instead of a number

In my ember app I want to reuse a model attribute as soon as the form is submitted. But the store seems to keep it as string unless I reload the whole route. I am using this and the following components:
Ember : 1.12.0
Ember Data : 1.0.0-beta.18
jQuery : 1.11.3
/app/models/purchase.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number'),
createdAt: DS.attr('date', {
defaultValue: function() { return new Date(); }
}),
.. other callback and associations..
});
/app/controllers/ledger/purchases/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
return {
newPurchase: this.store.createRecord('purchase', {
name: null,
amount: null,
player: null
})
}
}
});
/app/templates/ledger/purchases/new.hbs
<div class="row">
<div class="col-xs-12">
<h4>New purchase</h4>
<form>
<div class="form-group">
<label for="name" class="sr-only control-label">name</label>
{{input id='name' type="text" value=newPurchase.name placeholder="What" class="form-control"}}
</div>
<div class="form-group">
<label for="amount" class="sr-only control-label">amount</label>
{{input id='amount' type='number' value=newPurchase.amount placeholder="How much" class="form-control"}}
</div>
<div class="form-group">
<button type="submit" class="btn btn-success" {{action "create"}}>create</button>
{{#link-to 'ledger.purchases' tagName="button" class="btn btn-link" }}cancel{{/link-to}}
</div>
</form>
</div>
</div>
/app/controllers/ledger/purchases/new.js
import Ember from 'ember';
export default Ember.Controller.extend({
newPurchase: Ember.computed.alias('model.newPurchase'),
actions: {
create: function() {
var np = this.get('newPurchase');
console.log(Ember.typeOf(np.get('amount')));
........
save np etc...
}
}
});
the console log call clearly shows that the type is a string. The ember inspector shows the same. However data are correctly saved to the backend because after reloading everything is fine. But I need the amount as a number as soon as it is submitted because I use it to make and show the sum of all purchases.
Okay, I think I know what's going on. Setting input type to number won't help here. Value is still recognized as string. Usually when you submit form, backend anyway returns this value formatted as a number and problem's gone. You can see this even when you mock your data with a number, without a backend.
My solution would be to use a computed property for input component. Model:
export default DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number'),
createdAt: DS.attr('date', {
defaultValue: function() { return new Date(); }
}),
amountAsNum: Ember.computed('amount', {
get: function () {
return parseFloat(this.get('amount'));
},
set: function (key, value) {
var valueToSet = parseFloat(value);
this.set('amount', valueToSet);
return valueToSet;
}
}),
.. other callback and associations..
});
Template:
{{input id='amount' type='number' value=newPurchase.amountAsNum placeholder="How much" class="form-control"}}
Now, you can check typeof(amount) before save and it'll give you number. Demo on JS Bin.