Following the Angular (using 7.3.4) Hero Tutorial. Using angular with a django backend. I cannot seem to get a hero name to update:
I have a hero-detail.component.ts that has a save() method which should update the hero via the HeroService.updateHero() method. When i click the save button nothing happens at all...
I am suspicious that the heroService.updateHero method is pointing to the wrong url, but I am not sure where to point it or what to pass it to. Also using Pycharm and put in return this.http.put(this.heroesUrl, hero, httpOptions is red as an unresolved function, but i think this is just a Typescript Pycharm setting that should not matter.
Any pointers appreciated.
hero-detail.component.ts
import { Component, OnInit, Input } from '#angular/core';
import { Hero } from '../hero'
import { ActivatedRoute } from '#angular/router';
import { Location } from '#angular/common';
import { HeroService } from '../hero.service';
#Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.scss']
})
export class HeroDetailComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
#Input() hero: Hero;
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const number = +this.route.snapshot.paramMap.get('number');
this.heroService.getHero(number)
.subscribe(hero => this.hero = hero);
}
save(): void {
this.heroService.updateHero(this.hero)
.subscribe(() => this.goBack());
}
goBack(): void {
this.location.back();
}
hero.service.ts
import { Injectable } from '#angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { Observable, of } from 'rxjs'
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { catchError, map, tap } from 'rxjs/operators';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
#Injectable({
providedIn: 'root'
})
export class HeroService {
private heroesUrl = 'http://127.0.0.1:8000/heroes/'; // URL to web api
constructor(
private http : HttpClient
) { }
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
}
getHero(id:number): Observable<Hero>{
return this.http.get<Hero>(`${this.heroesUrl}${id}`);
}
updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, httpOptions)
}
}
}
heroes/views.py
from django.shortcuts import render
from rest_framework.response import Response
from .models import Hero
from rest_framework import generics
from .serializers import HeroSerializer
# Create your views here.
class HeroList(generics.ListAPIView):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
class Meta:
model = Hero
fields = ('number', 'name','id')
class HeroDetail(generics.GenericAPIView):
serializer_class = HeroSerializer
def get(self, request, id):
hero_detail = Hero.objects.get(id=id)
serializer = HeroSerializer(hero_detail)
return Response(serializer.data)
def put(self, request, id):
hero_detail = Hero.objects.get(id=id)
# hero_detail.name = request.data.get("name")
hero_detail.save()
serializer = HeroSerializer(hero_detail)
return Response(serializer.data)
class Meta:
model = Hero
fields = ('number', 'name','id')
hero-detail.component.html
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name">
</label>
</div>
</div>
<button (click)="goBack()">go back</button>
<button (click)="save()">save</button>
You should send the put request to '/heroes/hero_id/' for updates. With the restful APIs, in general, you should send
POST request to /heroes/ -> Create hero
GET request to /heroes/ -> List heroes
GET request to /heroes/id/ -> Retrieve single hero
PUT request to /heroes/id/ -> Update hero
DELETE request to /heroes/id/ -> Delete hero
So in your example, you should modify your updateHero method like this:
updateHero (hero: Hero): Observable<any> {
return this.http.put(`${this.heroesUrl}${hero.id}/` hero, httpOptions)
}
Related
DISCLAIMER: This is gonna be a pretty long description of a problem, to which I can't seem to find sources for that could help me. So any piece of information is appreciated!
I might add that I am fairly new to Angular and text script.
I have set up my backend with Django and set up the Rest Framework. These are my models:
models.py
class Person(models.Model):
name = models.CharField(max_length = 250, blank = True)
job = models.ForeignKey('Job', on_delete = models.CASCADE, null = True)
def __str__(self):
return self.name
class Job(models.Model):
job_name = models.CharField(max_length = 200, blank = False)
rank = models.PositiveIntegerField(default = 0, unique = True)
def __str__(self):
return self.job_name
In the backend I have set up different job names (Doctor, Teacher, etc...). So when I create a new Person, I get to choose from one of the different jobs, which have been created prior to this. (This is exactly how I woukd want my frontend to work)
Here are my serializer.py, views.py and urls.py files:
serializer.py
class PersonSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Person
fields = ('id', 'name', 'job')
class JobSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Job
fields = ('id', 'job_name')
views.py
class PersonViewSet(viewsets.ModelViewSet):
queryset = Person.objects.all()
serializer_class = PersonSerializer
def list(self, request, *args, **kwargs):
person = Person.objects.all()
serializer = PersonSerializer(person, many = True, context = {'request': request})
return Response(serializer.data)
class JobViewSet(viewsets.ModelViewSet):
queryset = Job.objects.all()
serializer_class = JobSerializer
def list(self, request, *args, **kwargs):
job = Job.objects.all()
serializer = JobSerializer(job, many = True, context = {'request': request})
return Response(serializer.data)
urls.py
router = routers.DefaultRouter()
router.register('person', views.PersonViewSet)
router.register('job', views.JobViewSet)
urlpatterns = [
path('', include(router.urls)),
]
The Rest Framework has successfully been integrated and I can now create, read, update and delete (CRUD) my models.
In Angular I have created a person component with a service, which contains very simple functions, to use CRUD on my frontend.
person.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class PersonService {
backendURL = 'http://127.0.0.1:8000'
httpHeaders = new HttpHeaders({'Content-Type': 'application/json'})
constructor(private http:HttpClient) { }
getPerson():Observable<any> {
return this.http.get(this.backendURL + '/person/',
{headers: this.httpHeaders});
}
getOnePerson(id):Observable<any> {
return this.http.get(this.backendURL + '/person/' + id + '/',
{headers: this.httpHeaders});
}
updatePerson(person): Observable<any> {
const body = { name: person.name, job: person.job, };
return this.http.put(this.backendURL + '/person/' + person.id + '/', body,
{headers: this.httpHeaders});
}
createPerson(person): Observable<any> {
const body = { name: person.name, job: person.job, };
return this.http.post(this.backendURL + '/person/', body,
{headers: this.httpHeaders});
}
deletePerson(id): Observable<any> {
return this.http.delete(this.backendURL + '/person/' + id + '/',
{headers: this.httpHeaders});
}
}
And my component ts file:
person.component.ts
import { Component, OnInit } from '#angular/core';
import { PersonService } from '../../services/person.service';
#Component({
selector: 'app-person',
templateUrl: './person.component.html',
styleUrls: ['./person.component.css'],
providers: [PersonService]
})
export class PersonComponent implements OnInit {
person_all = [{ name: ''}, ];
selectedPerson;
constructor(private api: PersonService) {
this.getPerson();
this.selectedPerson = { id:-1, name: '', job: '', }
}
getPerson = () => {
this.api.getPerson().subscribe(
data => {
this.person_all = data;
},
error => {
console.log(error);
}
)
}
personClicked = (person) => {
this.api.getOnePerson(person.id).subscribe(
data => {
this.selectedPerson = data;
},
error => {
console.log(error);
}
)
}
updatePerson = () => {
this.api.updatePerson(this.selectedPerson).subscribe(
data => {
this.getPerson();
},
error => {
console.log(error);
}
);
}
createPerson = () => {
this.api.createPerson(this.selectedPerson).subscribe(
data => {
this.person_all.push(data);
},
error => {
console.log(error);
}
);
}
deletePerson = () => {
this.api.deletePerson(this.selectedPerson.id).subscribe(
data => {
this.getPerson();
},
error => {
console.log(error);
}
);
}
ngOnInit(): void {
}
}
In HTML I created a very simple view, in where I can see every created Person and some buttons to create, update or delete some.
person.component.html
<h2>Person List</h2>
<ul>
<li *ngFor="let person of person_all">
<h2 (click)="personClicked(person)">{{person.nachname}}</h2>
</li>
</ul>
Name <input type="text" [(ngModel)]="selectedPerson.name"/><br>
Job <input type="text" [(ngModel)]="selectedPerson.job"/><br>
<button *ngIf="selectedPerson.id != -1" (click)="updatePerson()"> PUT </button>
<button *ngIf="selectedPerson.id == -1" (click)="createPerson()"> CREATE </button>
<button *ngIf="selectedPerson.id != -1" (click)="deletePerson()"> DELETE </button>
This is my problem: When I try to create a new Person, I don't see the different job names, which I have set up in the backend. I want to know, how do I get a "list" of jobs to select from, similar to how it works in Django with a Foreign Key?
Thanks for the read and thanks in advance!
I am new to and currently working on a Django and Angular webapp and I cant seem to be able to create and update my products from the angular web app and send them to the REST API to update the server. I have written a delete function and service request that works and I can retrieve the products from the API but I don't know how to write the functions for create products and update products. Would anyone be able to help me here?
Here is what I have so far:
api.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { Product } from './models/Product';
import { CookieService } from 'ngx-cookie-service';
import { Category } from './models/Category';
import { Shop } from './models/Shop';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class ApiService {
baseUrl = 'http://127.0.0.1:8000/';
baseProductUrl = `${this.baseUrl}app/products/`
baseCategoryUrl = `${this.baseUrl}app/categories/`
baseShopUrl = `${this.baseUrl}app/shops/`
headers = new HttpHeaders({
'Content-Type': 'application/json',
});
constructor(
private httpClient: HttpClient,
private cookieService: CookieService,
) { }
/* Product CRUD */
/* ADD: add a product to the server */
addProduct(): Observable<Product> {
const productUrl = `${this.baseProductUrl}`
return this.httpClient.post<Product>(productUrl, {headers: this.getAuthHeaders()})
}
// DELETE: delete the product from the server
deleteProduct(product_code: number): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.delete<Product>(productUrl, {headers: this.getAuthHeaders()})
}
// PUT: update the product on the server
updateProduct(product_code: Product): Observable<any> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.put(productUrl, {headers: this.getAuthHeaders()})
}
// GET: get all products from the server
getProducts() {
return this.httpClient.get<Product[]>(this.baseProductUrl, {headers: this.getAuthHeaders()});
}
// GET: get one product from the server
getProduct(product_code: number): Observable<Product> {
const productUrl = `${this.baseProductUrl}${product_code}/`
return this.httpClient.get<Product>(productUrl, {headers: this.getAuthHeaders()});
}
// GET: get all categories from the server
getCategories() {
return this.httpClient.get<Category[]>(this.baseCategoryUrl, {headers: this.getAuthHeaders()});
}
// GET: get one category from the server
getCategory(id: number): Observable<Category> {
const url = `${this.baseCategoryUrl}${id}/`;
return this.httpClient.get<Category>(url, {headers: this.getAuthHeaders()});
}
// GET: get all shops from the server
getShops() {
return this.httpClient.get<Shop[]>(this.baseShopUrl, {headers: this.getAuthHeaders()});
}
// GET: get one shop from the server
getShop(business_reg: string): Observable<Shop> {
const url = `${this.baseShopUrl}${business_reg}/`;
return this.httpClient.get<Shop>(url, {headers: this.getAuthHeaders()});
}
loginUser(authData: any) {
const body = JSON.stringify(authData);
return this.httpClient.post(`${this.baseUrl}auth/`, body, {headers: this.headers});
}
registerUser(authData: any) {
const body = JSON.stringify(authData);
return this.httpClient.post(`${this.baseUrl}user/users/`, body, {headers: this.headers});
}
getAuthHeaders() {
const token = this.cookieService.get('ur-token')
return new HttpHeaders({
'Content-Type': 'application/json',
Authorization: `Token ${token}`
});
}
}
dashboard.component.ts
import { Component, OnInit } from '#angular/core';
import { Product } from 'src/app/models/Product';
import { CookieService } from 'ngx-cookie-service';
import { Router } from '#angular/router';
import { ApiService } from '../../api.service';
import { Category } from '../../models/Category';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
products: Product[] =[]
constructor(
private cookieService: CookieService,
private router: Router,
private apiService: ApiService
) { }
ngOnInit(): void {
this.getProducts()
}
getProducts(): void {
const urToken = this.cookieService.get('ur-token')
if (!urToken) {
this.router.navigate(['/auth']);
} else {
this.apiService.getProducts().subscribe(
data => {
this.products = data;
console.log('selectedProduct', this.products);
},
error => console.log(error)
);
}
}
delete(product: Product): void {
this.products = this.products.filter(h => h !== product);
this.apiService.deleteProduct(product.product_code).subscribe();
}
}
dashboard.component.html
<section>
<div class="container">
<h1>My Dashboard <button class="btn" routerLink="add/">Add Product</button></h1>
<div class="card-wrapper">
<div class="card" *ngFor="let product of products">
<!-- card left -->
<div class="product-content">
<img class="img-fluid image" src="{{product.product_image}}">
<div class="product-item">
<h2 class="product-title">{{ product.name }}</h2>
<a class="edit"><button class="btn" routerLink="edit/{{ product.product_code }}">Edit</button></a>
<a class="delete"><button class="btn" (click)="delete(product)">Delete</button></a>
</div>
</div>
</div>
</div>
</div>
</section>
product-form.component.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { ApiService } from 'src/app/api.service';
import { Product } from 'src/app/models/Product';
import { Location } from '#angular/common';
import { FormGroup, FormControl } from '#angular/forms';
#Component({
selector: 'app-product-form',
templateUrl: './product-form.component.html',
styleUrls: ['./product-form.component.css']
})
export class ProductFormComponent implements OnInit {
product!: Product;
constructor(
private route: ActivatedRoute,
private apiService: ApiService,
private location: Location
) { }
ngOnInit(): void {
this.getProduct()
}
getProduct(): void {
const product_code = Number(this.route.snapshot.paramMap.get('product_code'));
this.apiService.getProduct(product_code)
.subscribe(product => this.product = product);
}
goBack(): void {
this.location.back();
}
save(): void {
this.apiService.updateProduct(this.product)
.subscribe(() => this.goBack());
}
}
product-form.component.html
<div *ngIf="product">
<input id="product-code" [(ngModel)]="product.product_code" placeholder="product_code">
<input id="product-name" [(ngModel)]="product.name" placeholder="name">
<input id="product-price" [(ngModel)]="product.price" placeholder="price">
</div>
Looking at your API service the HTTP POST and PUT calls are missing payload data.
Can you try passing the data for your product as a parameter?
The product code for the PUT should be in a class with populated data (from your component) so that your backend API can pick up the code (and other product data fields) and update your data. Your backend API methods should then read the request data from the body (the payload).
For your Angular API service methods you should pass product data parameters from your component as follows:
/* ADD: add a product to the server */
addProduct(product: Product): Observable {
const productUrl = ${this.baseProductUrl}
return this.httpClient.post(productUrl, product, {headers: this.getAuthHeaders()})
}
// PUT: update the product on the server
updateProduct(product: Product): Observable {
const productUrl = ${this.baseProductUrl}${product_code}/
return this.httpClient.put(productUrl, product {headers: this.getAuthHeaders()})
}
There is a post (https://andrewhalil.com/2020/12/01/calling-web-apis-from-your-angular-ui/) on my site that shows how this is done in Angular.
I am following the HeroTutorial but using a Django backend. I have an Hero object that I am getting from DRF endpoint (verified with Postman). In my hero-detail.html the hero.name and hero.id are not displaying anything.
I know the hero object is being passed to the hero-detail.html because the browser shows the "Details" and "id:" so the line <div *ngIf="hero"> is telling me that there is a hero..
But if there is a hero why does hero.name not show anything?
There are no errors in the browser console. The link to get to the hero-detail.component is coming from a dashboard.component which uses the same method but for some reason hero.name and hero.number work fine. The dashboard.component.html displays correctly so I know my services are working fine.
My hero-detail.html
<div *ngIf="hero">
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.number}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name">
</label>
</div>
</div>
<button (click)="goBack()">go back</button>
hero-detail.component
import { Component, OnInit, Input } from '#angular/core';
import { Hero } from '../hero'
import { ActivatedRoute } from '#angular/router';
import { Location } from '#angular/common';
import { HeroService } from '../hero.service';
#Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.scss']
})
export class HeroDetailComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
#Input() hero: Hero;
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const number = +this.route.snapshot.paramMap.get('number');
this.heroService.getHero(number)
.subscribe(hero => this.hero = hero);
}
goBack(): void {
this.location.back();
}
}
dashboard.component
import { Component, OnInit } from '#angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: [ './dashboard.component.scss' ]
})
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
}
dashboard.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4"
routerLink="/detail/{{hero.number}}">
<div class="module hero">
<h1>{{hero.name}}</h1>
</div>
</a>
</div>
hero.service
import { Injectable } from '#angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { Observable, of } from 'rxjs'
import { HttpClient, HttpHeaders } from '#angular/common/http'
import { catchError, map, tap } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class HeroService {
private heroesUrl = 'http://127.0.0.1:8000/heroes/'; // URL to web api
constructor(
private http : HttpClient
) { }
/**
* Handle Http operation that failed.
* Let the app continue.
* #param operation - name of the operation that failed
* #param result - optional value to return as the observable result
*/
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
}
getHero(number:number): Observable<Hero>{
return this.http.get<Hero>(`${this.heroesUrl}${number}`);
}
// getHero(number: number): Observable<Hero> {
// return of(HEROES.find(hero => hero.number === number));
//}
}
hero.service endpoint response from localhost:8000/heroes/2, taken from Postman:
[
{
"name": "better hero",
"number": 2
}
]
also hero.service endpoint response from localhost:8000/heroes, taken from Postman:
[
{
"name": "bad hero",
"number": 7
},
{
"name": "bad hero",
"number": 7
},
{
"name": "better hero",
"number": 2
}
]
views.py
class HeroList(generics.ListAPIView):
queryset = Hero.objects.all()
serializer_class = HeroSerializer
class Meta:
model = Hero
fields = ('number', 'name')
class HeroDetail(generics.GenericAPIView):
serializer_class = HeroSerializer
#TODO why do i need many=True here, this should returning one instance
def get(self, request, number):
# number = self.request.query_params.get('number')
hero_detail = Hero.objects.filter(number=number)
serializer = HeroSerializer(hero_detail, many=True)
return Response(serializer.data)
class Meta:
model = Hero
fields = ('number', 'name')
Looking at the sample API responses you've posted, it looks like retrieve method for a hero (/heroes/2 for instance) returns a list with only one item, instead of returning the item itself. In your client code though, you expect a hero object, not a hero list. Depending on your client code and for a rest api in general,
localhost:8000/heroes/2 should return
{
"name": "better hero",
"number": 2
}
not
[
{
"name": "better hero",
"number": 2
}
]
I'm using Angular 6 and Django REST Framework
The view of DRF is
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return AmountGiven.objects.filter(
contact__user=self.request.user
)
def perform_create(self, serializer):
save_data = {}
print(self.request.POST)
# validate user and to save_data dictionary
contact_pk = self.request.POST.get('contact', None)
print(contact_pk)
if not contact_pk:
raise ValidationError({'contact': ['Contact is required']})
contact = Contact.objects.filter(
user=self.request.user,
pk=contact_pk
).first()
if not contact:
raise ValidationError({'contact': ['Contact does not exists']})
# add contact to save_data dictionary
save_data['contact'] = contact
# process mode_of_payment is in request
mode_of_payment_pk = self.request.POST.get('mode_of_payment', None)
if mode_of_payment_pk:
mode_of_payment = ModeOfPayment.objects.get(pk=mode_of_payment_pk)
if not mode_of_payment:
raise ValidationError({'mode_of_payment': ['Not a valid mode of payment']})
# add mode_of_payment to save_data dictionary
save_data['mode_of_payment'] = mode_of_payment
# pass save_data dictionary to save()
serializer.save(**save_data)
AmountGivenSerializer in serializers.py
class AmountGivenSerializer(serializers.ModelSerializer):
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'interest_rate', 'duration', 'given_date', 'promised_return_date',
'mode_of_payment', 'transaction_number', 'interest_to_pay', 'total_payable', 'amount_due',
'total_returned', 'comment', 'modified', 'created'
)
and in Angular component
import { Component, OnInit } from '#angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from '#angular/forms';
import {AmountGiven} from '../amount-given.model';
import {AmountGivenService} from '../amount-given.service';
import {ActivatedRoute} from '#angular/router';
#Component({
selector: 'app-amount-given-add',
templateUrl: './amount-given-add.component.html',
styleUrls: ['./amount-given-add.component.css']
})
export class AmountGivenAddComponent implements OnInit {
addMoneyForm: FormGroup;
submitted = false;
contact_id: string;
amountGiven: AmountGiven;
constructor(
private formBuilder: FormBuilder,
private amountGivenService: AmountGivenService,
private route: ActivatedRoute
) { }
ngOnInit(): void {
this.route.params.subscribe(
param => {
this.contact_id = param['contact_id'];
}
);
this.addMoneyForm = this.formBuilder.group({
amount: new FormControl('', [
Validators.required
]),
interest_rate: new FormControl(),
duration: new FormControl(),
given_date: new FormControl(),
promised_return_date: new FormControl(),
transaction_number: new FormControl(),
mode_of_payment: new FormControl(),
comment: new FormControl()
});
}
get f() {
return this.addMoneyForm.controls;
}
onSubmit() {
this.submitted = true;
// stop here if form is invalid
if (this.addMoneyForm.invalid) {
console.log('invalid');
return;
}
const data = this.addMoneyForm.value;
data.contact = this.contact_id;
this.amountGivenService.add(data).subscribe(res => {
console.log('req completed', res);
});
}
}
But when form is submited it returns validation error from server for contact field.
{"contact":["Contact is required"]}
The request header has the contact parameter
sending the same request from Postman is working fine. The Javascript XHR code of Postman request is
var data = new FormData();
data.append("amount", "5000");
data.append("contact", "65827a1f-003e-4bb3-8a90-6c4321c533e6");
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("POST", "https://koober-production.herokuapp.com/api/amount-given/");
xhr.setRequestHeader("Authorization", "Bearer ATjIuQ6hLzc55wHaXIzHmcKafEzk1B");
xhr.setRequestHeader("Cache-Control", "no-cache");
xhr.setRequestHeader("Postman-Token", "28d9d33a-f0a6-431c-8936-da4f6565ece4");
xhr.send(data);
could not understand this issue is related to Angular or Django because Postman request is working fine with Djano and Angular is sending the request in the parameter.
Edit 2: AmountGivenService
import { Injectable } from '#angular/core';
import {ResourceProviderService} from '../../resource-provider.service';
import {AuthService} from '../../auth/auth.service';
import {Observable, of} from 'rxjs';
import {AmountGiven} from './amount-given.model';
import {AppHttpClient} from '../../app-http-client';
#Injectable({
providedIn: 'root'
})
export class AmountGivenService {
private url = 'amount-given/';
constructor(
private http: AppHttpClient
) { }
add(data): Observable<AmountGiven> {
return this.http.Post<AmountGiven>(this.url, data);
}
}
which is furhter using AppHttpClient
import { Injectable } from '#angular/core';
import {HttpClient, HttpHeaders, HttpParams} from '#angular/common/http';
import {Observable, of} from 'rxjs';
import {catchError} from 'rxjs/operators';
import {ResourceProviderService} from './resource-provider.service';
export interface IRequestOptions {
headers?: HttpHeaders;
observe?: 'body';
params?: HttpParams;
reportProgress?: boolean;
responseType?: 'json';
withCredentials?: boolean;
body?: any;
}
export function appHttpClientCreator(http: HttpClient, resource: ResourceProviderService) {
return new AppHttpClient(http, resource);
}
#Injectable({
providedIn: 'root'
})
export class AppHttpClient {
private api_url = this.resource.url + '/api/';
constructor(
public http: HttpClient,
private resource: ResourceProviderService
) {
}
public Post<T>(endPoint: string, params: Object, options?: IRequestOptions): Observable<T> {
return this.http.post<T>(this.api_url + endPoint, params, options)
.pipe(
catchError(this.handleError('post', endPoint, null))
);
}
private handleError<T> (operation = 'operation', endpoint = '', result?: T) {
return (error: any): Observable<T> => {
console.error(error);
console.log(error.message);
console.log(operation);
console.log(endpoint);
return of(result as T);
};
}
}
The problem is that you're trying to access:
self.request.POST.get('contact', None)
in DRF you need to do:
self.request.data.get('contact', None)
It works from postman because you're constructing a FormData object which is fundamentally different from the JSON body angular will send and that is expected by a RESTful API like DRF.
from a more general standpoint, in DRF, serializers should perform the request validation using the built in validators, rather than manually validating in the view.
I'm hesitant to add this as the answer just yet, but it feels like you need to add something along the lines of:
<form method="POST" action="url/to/your-DRF-endpoint" enctype="application/x-www-form-urlencoded">
....
</form>
Either within your template or within your Angular POST request code you need to make sure the data is posted as application/x-www-form-urlencoded.
Essentially, Django expects application/x-www-form-urlencoded, and without it your self.request.POST object will be empty and hence self.request.POST.get('contact', None) will be empty and default to None.
I'm new to Angular and I'm creating a test Application to accelerate my understanding of the topic. Recently I encountered a challenge to integrate Angular2(FrontEnd) with Django(Backend) by fetching the data using REST APIs.
File: library.service.ts
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Rx';
import { Injectable } from '#angular/core';
import { Headers, Http, Response } from '#angular/http';
// Project Modules
import { Library } from '../models';
#Injectable()
export class LibraryService {
private librariesUrl = 'http://127.0.0.1:8000/api/library/create-library/';
constructor(private http: Http) { }
private headers = new Headers({'Content-Type': 'application/json'});
private extractData(res: Response) {
return res.json();
}
private handleError (error: any) {
return Observable.throw(error.message || 'Server error');
}
getAll(): Observable<Library[]> {
return this.http.get(this.librariesUrl, {headers: this.headers})
.map((res) => this.extractData(res.json())).catch((err) => this.handleError(err));
}
}
File: libraries.component.ts
import { Component, OnInit} from '#angular/core';
import {HttpClient} from '#angular/common/http';
// Project Modules
import { Library } from '../models';
import { LibraryService } from './library.service';
#Component({
selector: 'app-libraries',
templateUrl: './libraries.component.html',
styleUrls: ['./libraries.component.css'],
})
export class LibrariesComponent implements OnInit {
libraries: Library[];
personalLibraries: Library[];
collaborativeLibraries: Library[];
constructor(private libraryService: LibraryService, private http: HttpClient) { }
ngOnInit(): void {
/*this.http.get('http://127.0.0.1:8000/api/library/create-library/').subscribe((data: Library[]) => {
console.log(data);
this.personalLibraries = data;
});*/
this.libraryService.getAll().subscribe(response => this.personalLibraries = response);
}
}
REST API
# Django Modules
from django.shortcuts import get_object_or_404
# REST Modules
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view, authentication_classes, permission_classes
# Project Modules
from .models import Resource, ResourceUserAssociation, Collection, Library
from mysite.utils import get_value_or_404, get_value_or_default, get_boolean
from .serializers import LibrarySerializer, CollectionSerializer
# TODO: Check user authentication here
class CreatorLibraryAPI(APIView):
def get(self, request, format=None):
# slug = get_value_or_404(request.GET, 'slug')
lib_object = Library.objects.filter(type='personal')
sdata = LibrarySerializer(lib_object, many=True).data
return Response(sdata, status=status.HTTP_200_OK)
JSON I'm Expecting
[
{
"slug": "tech-stack",
"title": "Technology Stack",
"description": "Library of resources related to Technology",
"type": "personal"
},
{
"slug": "biz-stack",
"title": "Technology Stack",
"description": "Library of resources related to Business",
"type": "personal"
},
{
"slug": "design-stack",
"title": "Design Stack",
"description": "Library of resources related to Design",
"type": "personal"
}
]
Important
When I try to fetch data in the Component only, then I successfully get the result [See the commented code in libraries.components.ts]. But somehow it's not working in the Service, am I doing something wrong with Observables?
Note
This problem is very similar to Question here.
Big thanks to the community in advance :)
Few changes I've made:
Used HttpClient instead of Http. This allows me to remove .map() as HttpClien already returns the JSON (instead of the whole response).
Correct File: library.service.ts
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Rx';
import { Injectable } from '#angular/core';
import { Headers } from '#angular/http';
import {HttpClient} from '#angular/common/http';
// Project Modules
import { Library } from '../models';
#Injectable()
export class LibraryService {
private librariesUrl = 'http://127.0.0.1:8000/api/library/create-library/';
constructor(private http: HttpClient) { }
private headers = new Headers({'Content-Type': 'application/json'});
private handleError (error: any) {
console.log('---------- CATCH ERROR ----------');
return Observable.throw(error.message || 'this is some random server error');
}
getAll(): Observable<Library[]> {
return this.http.get(this.librariesUrl).catch((err) => this.handleError(err));
}
}