Angular2 How to POST data using a service class - web-services

I have a simple application form which I am trying to post to the server. I am fairly new to Angular2
How can I pass the data from the component to the service and onto the server for a POST request.
The POST is working fine when I try it directly from FireFox plugin 'httpRequester'
This is the TaskComponent.ts
#Component({
selector: 'tasks',
template: `<div mdl class="mdl-grid demo-content">
<div class="demo-graphs mdl-shadow--2dp mdl-color--white mdl-cell mdl-cell--8-col">
<h3>Create Task Page</h3>
<form action="#" (ngSubmit)="onSubmit()">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" pattern="[A-Z,a-z]*" id="taskname" [(ngModel)]="data.taskname"/>
<label class="mdl-textfield__label" for="taskname">Task Name</label>
<span class="mdl-textfield__error">Only alphabet and no spaces, please!</span>
</div>
<button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored" type="submit">Create Task</button>
</form>
`,
directives: [ROUTER_DIRECTIVES, MDL]
})
export class CreateTaskComponent {
data: any
constructor() {
this.data = {
//taskname: 'Example Task'
};
}
onSubmit(form) {
console.log(this.data.taskname); <--Data is passed upon submit onto the console. Works fine.
//Need to call the postApartment method of ApartmentService
}
}
ApartmentService.ts
import {Http, Response} from 'angular2/http'
import {Injectable} from 'angular2/core'
import 'rxjs/add/operator/map';
#Injectable()
export class ApartmentService {
http: Http;
constructor(http: Http) {
this.http = http;
}
getEntries() {
return this.http.get('./api/apartments').map((res: Response) => res.json());
}
getProfile(userEmail :string){
return this.http.get(`./api/apartments/getprofile/${userEmail}`).map((res: Response) => res.json());
}
postApartment(){
// Not familiar with the syntax here
}
}
Server.ts
router.route('/api/apartments')
.post(function(req, res) {
var apartment = new Apartment();
apartment.name = req.body.name;
apartment.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Apartment created!' });
});
})

You can inject service via dependency injection and use it in the component
export class CreateTaskComponent {
constructor(){private _apartmentService: ApartmentService}{}
}
And you can access this in any of the component function via
onSubmit(form) {
console.log(this.data.taskname); <--Data is passed upon submit onto the console. Works fine.
//Need to call the postApartment method of ApartmentService
this._apartmentService.postApartment()
}
And when bootstraping the component you have to add it as dependency via
bootstrap(AppComponent, [ApartmentService]);
Another option for doing the last step is by added providers in the Component decorator like
#Component{
providers: [ApartmentService]
}

Inject the apartmentService in the component, No need of providers as I have bootstrapped it. (If you bootstartp the service, Do not include it in providers. It breaks the system)
export class CreateTaskComponent {
data: any
constructor(private apartmentService: ApartmentService) {
this.data = {};
}
onSubmit(form) {
this.apartmentService.postApartment(this.data);
}
}
The critical piece is the postApartment() method in the service
postApartment(data :any){
return this.http.post('/api/apartments',
JSON.stringify(data),{headers : new Headers({'Content-Type':'application/json'})
})
.map((res: Response) => res.json()).subscribe();
}
Also make sure on the server.js code, the mongoose fields match the http body parameters being passed.
I had to fix it to make it work.

Related

AWS Graphql Error - Amplify mutations cannot write to dynamoDB

I am new to the AWS world and have been working on an Amplify project for a few months. Project uses Amplify front-end with Appsync / GraphQL and dynamodb all set up via the console in Cloud9. Read access to the backend using the graphql queries works great but I simply cannot get any of the auto-generated mutations to work. All throw ""Request aborted" errors with ""Invalid URI format".
Here's a snippet of the error
Here's a simple example from my code:
import { createCategory} from './graphql/mutations';
//Here's the start of our Javascrpt App
const WriteTestPage = () => {
//define function to handle submitButton, which is used to create a new customer
async function handleSubmit () {
try {
//For Customers, the name is required
console.log('Called our API - top');
const categoryDetails = {
description: 'My new category'
}
//Call GraphQL with correct parameters to create in backend
const returnedCategory = await API.graphql(graphqlOperation(createCategory, { input: categoryDetails}));
console.log('Called our API');
} catch (err) {
console.log('error creating category', err);
}
}
Here's the corresponding schema. I've opened up all access and have a user pool as well as an API key.
type Category
#model
#auth(
rules: [
{ allow: groups, groups: ["superAdmin"]} #allow the admin to crud the table
{ allow: private, operations: [read, update, create, delete]} #allow the authenticated users to read data from the table
{ allow: public, operations: [read, update, create, delete]} #DEV only - allow the UNauthenticated users to read data from the table
])
{
id: ID!
description: String!
conectivity: [Conectivity] #connection(keyName: "byCategory", fields: ["id"]) #Every Category may have zero or more conectivities
}
Here's the network trace. Doesn't appear that the front-end is even issuing a POST to the backend.
Network trace
First post on StackOverflow so appologies for any errors. Any help would be much appreciated as I'm beating my head against the wall here. I'm sure it's something simple that I'm overlooking.
Full code illustrating error:
import './App.css';
//context testing and dev
import React, { useEffect, useState } from 'react';
import Amplify, { AUTH, API, graphqlOperation } from 'aws-amplify';
import { Link } from 'react-router-dom';
//Queries and Mutations
import { createCategory} from './graphql/mutations';
import {AmplifyAuthenticator, AmplifySignOut } from '#aws-amplify/ui-react';
//Here's the start of our Javascrpt App
const WriteTestPage = () => {
//define function to handle submitButton, which is used to create a new customer
async function handleSubmit () {
try {
//For Customers, the name is required
console.log('Called our API - top');
const categoryDetails = {
description: 'My new catetory'
}
//Call GraphQL with correct parameters to create in backend
const returnedCategory = await API.graphql(graphqlOperation(createCategory, { input: categoryDetails}));
console.log('Called our API');
} catch (err) {
console.log('error creating category', err);
}
}
//Define HTML to return in order to create page
return (
<div className="App">
<section>
<AmplifyAuthenticator>
<section>
<h3 className="sectionHeader">Write Test</h3>
<center><form onSubmit={handleSubmit}>
<div >
<table className="tableStyle">
<thead>
<tr>
<th className="tableHeaderStyle">Write Test</th>
</tr>
</thead>
<tbody>
<tr className="tableRowOddStyle">
<td className="tableDataStyle">
<button className="btn" type="submit" className="buttonStyle">Add</button>
</td>
</tr>
</tbody>
</table>
</div>
</form></center>
<p></p>
</section>
</AmplifyAuthenticator>
</section>
</div>
);
}
export default WriteTestPage;

How to maintain authentication data

I am doing a Vue 3 practice together with Django Rest Framework, what I am trying to do is a token authentication validation, a user logs in, a token is going to be generated, but I run into a problem and it is that when at moment of logging in is done correctly and I am able to obtain the generated token, the problem is that when reloading the page the token is no longer in the vue application, a possible solution that I decided is to make the token save in local storage, but i think it is not the correct solution.
This is my Login.vue:
<template>
<h2>login</h2>
<form method="POST" #submit.prevent="sendData" autocomplete="off">
<input
type="text"
placeholder="Nombre de Usuario"
v-model.trim="username"
/>
<input
type="password"
placeholder="ContraseƱa de Usuario"
v-model.trim="password"
/>
<button type="submit">enviar</button>
</form>
</template>
<script>
import { ref } from '#vue/reactivity';
import { watchEffect } from '#vue/runtime-core';
export default {
setup() {
const username = ref('');
const password = ref('');
const token = ref('');
const sendData = () => {
fetch(`http://localhost:8000/auth-token/`, {
method: 'POST',
body: JSON.stringify({
username: username.value,
password: password.value,
}),
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.catch((error) => console.error('Error:', error))
.then((response) => {
token.value = response.token;
});
};
watchEffect(() => localStorage.setItem('Token', token.value));
return {
username,
password,
sendData,
};
},
};
</script>
Here is one of my latest answer on the question: https://stackoverflow.com/a/66872372/8816585
TLDR: you only have a few possibilities to persist the data on the frontend but using localStorage/cookies/IndexedDB is totally fine for this purpose.
Also, making a call to the backend at the start of your app is a good idea too, especially if the payload is a bit heavy (you send some JWT, and get a load of personal infos).
Here is a list of packages that may help you persist data on the frontend: https://github.com/vuejs/awesome-vue#persistence

Angular 2 ngFor object property change not updated its view

I have did the following
<div *ngFor="let item of documentData">
<polymer-component [data]="item"></polymer-component>
</div>
<button (click)="ChangePropertyValue()">ChangePropertyValue</button>
ChangePropertyValue(){
this.documentData[0].documentname="Document changed";
}
ngOnInit(){
this.documentData={"documentname":"Document"}
}
Polymer-Component has properties such as
documentname
When firing the ChangePropertyValue() , the object and its property is being updated but not its view. Please provide solution as soon as possible.
I have also tried ChangeDetectorRef,still it results nothing
Try running inside of angular's zone
import { NgZone } from '#angular/core';
constructor(private ngZone: NgZone) {
}
ChangePropertyValue(){
this.ngZone.run(() => {
this.documentData[0].documentname="Document changed"
})
}

Angular 2 Unit Testing - Cannot read property 'root' of undefined

Error Description
Angular version: 2.3.1
My unit test fails to create the component - I know this issue is related to the [routerLink] and [routerLinkActive] directives because removing them from the template allows the test to create the component.
TEMPLATE
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button class="navbar-toggle" data-toggle="collapse" data-target="#iotahoe-top-navigation">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" [routerLink]="['/']">IoTahoe</a>
</div>
<div class="collapse navbar-collapse" id="iotahoe-top-navigation">
<ul *ngIf="isAuthenticated()" class="nav navbar-nav navbar-right">
<li [routerLinkActive]="['active']"><a [routerLink]="['/dashboard']">Dashboard</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/browse']">Browse</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/admin']">Admin</a></li>
<li [routerLinkActive]="['active']"><a (click)="onLogout()" style="cursor: pointer;">Logout</a></li>
</ul>
</div>
TYPESCRIPT
import { Component, OnInit } from '#angular/core';
import { AuthenticationService } from '../../authentication/authentication.service';
import { Router } from '#angular/router';
#Component({
moduleId: module.id.toString(),
selector: 'app-top-navbar',
templateUrl: './top-navbar.component.html',
styleUrls: ['./top-navbar.component.css']
})
export class TopNavbarComponent implements OnInit {
constructor(private authenticationService: AuthenticationService, private router: Router) { }
ngOnInit() {
}
isAuthenticated() {
return this.authenticationService.isLoggedIn;
}
onLogout() {
this.authenticationService.logout().subscribe(() => {
return this.router.navigate(['/login']);
});
}
}
TEST SPEC
/* tslint:disable:no-unused-variable */
import {async, ComponentFixture, TestBed, inject} from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import {DebugElement, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, Component} from '#angular/core';
import { RouterTestingModule } from '#angular/router/testing';
import { Location, CommonModule } from '#angular/common';
import { TopNavbarComponent } from './top-navbar.component';
import { AuthenticationService } from '../../authentication/authentication.service';
import { Router } from '#angular/router';
import {ReactiveFormsModule} from "#angular/forms";
#Component({
template: ''
})
class DummyComponent {
}
describe('TopNavbarComponent', () => {
let component: TopNavbarComponent;
let fixture: ComponentFixture<TopNavbarComponent>;
let authenticationService: AuthenticationService;
beforeEach(async(() => {
const authenticationServiceStub = {
isLoggedIn: false
};
const routerStub = {
navigate: jasmine.createSpy('navigate'),
navigateByUrl: jasmine.createSpy('navigateByUrl')
};
TestBed.configureTestingModule({
declarations: [ TopNavbarComponent, DummyComponent ],
imports:[CommonModule, ReactiveFormsModule, RouterTestingModule.withRoutes(
[
{ path: '/', component:DummyComponent },
{ path: '/login', component:DummyComponent },
{ path: '/dashboard', component:DummyComponent },
{ path: '/browse', component:DummyComponent },
{ path: '/admin', component:DummyComponent }
])],
providers: [
{ provide: AuthenticationService, useValue: authenticationServiceStub },
{ provide: Router, useValue: routerStub }
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TopNavbarComponent);
component = fixture.componentInstance;
authenticationService = TestBed.get(AuthenticationService);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
ERROR
zone.js:155 Uncaught Error: Error in package:407:9:6 caused by: Cannot
read property 'root' of undefined
at ViewWrappedError.Error (native)
at ViewWrappedError.ZoneAwareError (localhost:9876/base/src/test.ts:133296:33)
at ViewWrappedError.BaseError [as constructor] (localhost:9876/base/src/test.ts:35630:16)
at ViewWrappedError.WrappedError [as constructor] (localhost:9876/base/src/test.ts:35695:16)
at new ViewWrappedError (localhost:9876/base/src/test.ts:68018:16)
at DebugAppView._rethrowWithContext (localhost:9876/base/src/test.ts:108242:23)
at DebugAppView.create (localhost:9876/base/src/test.ts:108142:18)
at DebugAppView.View_TopNavbarComponent_Host0.createInternal (/DynamicTestModule/TopNavbarComponent/host.ngfactory.js:16:19)
at DebugAppView.AppView.createHostView (localhost:9876/base/src/test.ts:107700:21)
at DebugAppView.createHostView (localhost:9876/base/src/test.ts:108156:52)
at ComponentFactory.create (localhost:9876/base/src/test.ts:49830:25)
at initComponent (localhost:9876/base/src/test.ts:6425:53)
at ZoneDelegate.invoke (localhost:9876/base/src/test.ts:132727:26)
at ProxyZoneSpec.onInvoke (localhost:9876/base/src/test.ts:95802:39)
at ZoneDelegate.invoke (localhost:9876/base/src/test.ts:132726:32)Zone.runTask #
zone.js:155ZoneTask.invoke # zone.js:345data.args.(anonymous function)
# zone.js:1376
The routerLink directives need a real router, but you are mocking it. A couple things I can see you doing:
If you don't plan to click the links during testing, then you can mock those directive to just make "noop" directives so the compiler doesn't complain. e.g.
#Directive({
selector: '[routerLink], [routerLinkActive]'
})
class DummyRouterLinkDirective {}
Then just add that to the declarations of the test module. With this you don't really need to configure the RouterTestingModule at all. You could probably get rid of that.
Also if you don't plan to click test, another option (without needing to create dummy directives is to just ignore the errors of missing directives:
schemas: [ NO_ERRORS_SCHEMA ]
You would add this to the test module configuration (as seen here). This might not be desirable in some cases, as it could also ignore errors that you actually want detected, which could lead to hard to debug tests.
If you would actually like to click the links and test routing, then you can use the real router. You can see an example of how you can test the navigation, using the Location, as seen in this post.
For me the solution with the non-mocked routing worked. But I found out that I also needed to add a
<router-outlet></router-outlet>
to the component using "routerlink active".

Angular 2 http post reloads page, logout doesn't delete session cookie

I have an Angular 2 service that has a logout function. When the function is called from the app component it cause a full page refresh. When using angular 1 projects I haven't experienced this behavior. If I call my logout endpoint with postman the session cookie is deleted. It is not deleted if I use my angular 2 authentication service.
Service
import {Injectable} from 'angular2/core';
import {User} from './user';
import {Headers, RequestOptions, Http, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import {Cookie} from '../extensions/cookies';
#Injectable()
export class AuthenticationService {
constructor (private http: Http) {}
private _prepTestHost = 'http://localhost:8000/';
private _prepTestLoginUrl = this._prepTestHost + 'login/';
private _prepTestLogoutUrl = this._prepTestHost + 'logout/';
private _authenticated: boolean;
getUser() {}
isAuthenticated() {
return this._authenticated;
}
setAuthenticated() {
this._authenticated = true;
}
loginUser(username, password) : Observable<User> {
let body = JSON.stringify({username, password});
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(this._prepTestLoginUrl, body, options)
.map(res => <User> res.json(), this.setAuthenticated())
.catch(this.handleError)
}
logoutUser() : Observable<void> {
let body = JSON.stringify({});
let csrfcookie = Cookie.getCookie('csrftoken');
let headers = new Headers({
'X-CSRFToken': csrfcookie,
'Content-Type': 'application/json'
});
let options = new RequestOptions({ headers: headers});
return this.http.post(this._prepTestLogoutUrl, body, options)
.map(res => <void> res.json())
.catch(this.handleError);
}
private handleError (error: Response) {
// in a real world app, we may send the server to some remote logging infrastructure
// instead of just logging it to the console
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
App Component
import {Component} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {WelcomeCenterComponent} from './welcome-center/welcome-center.component';
import {AuthenticationService} from './authentication/authentication.service';
import {LoginModalComponent} from './authentication/login-modal.component';
import {BrowserXhr, HTTP_PROVIDERS} from "angular2/http";
import {CORSBrowserXHR} from './extensions/corsbrowserxhr';
import {provide} from "angular2/core";
#Component({
selector: 'my-app',
template: `
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#" [routerLink]="['WelcomeCenter']">Brand</a>
</div>
<ul class="nav navbar-nav navbar-right">
<li *ngIf="!authenticated()">
Login
</li>
<li *ngIf="authenticated()">
Logout
</li>
</ul>
</div>
</nav>
<router-outlet></router-outlet>
<login-modal></login-modal>
`,
directives: [ROUTER_DIRECTIVES, LoginModalComponent],
providers: [HTTP_PROVIDERS,
provide(BrowserXhr, {useClass: CORSBrowserXHR}),
AuthenticationService]
})
#RouteConfig([
{
path: '/welcome-center/...',
name: 'WelcomeCenter',
component: WelcomeCenterComponent,
useAsDefault: true
}
])
export class AppComponent {
constructor(private _authenticationService: AuthenticationService) {}
authenticated() {
return this._authenticationService.isAuthenticated();
}
logout() {
console.log("Logout button pressed");
this._authenticationService.logoutUser().subscribe();
}
}
Setting withCredentials attribute:
import {BrowserXhr, HTTP_PROVIDERS} from "angular2/http";
import {Injectable, provide} from "angular2/core";
#Injectable()
export class CORSBrowserXHR extends BrowserXhr{
build(): any{
var xhr:any = super.build();
xhr.withCredentials = true;
return xhr;
}
}
I think that the page reload is due to the fact that you don't prevent event propagation when blocking on the layout button (you an 'a' HTML element with an 'href' attribute). You could use 'return false' at the end of your logout function or '$event.stopPropagation()'.
See the question for more details:
Stop event propagation in Angular 2
Regarding the cookie problem, I these that you use cross domain requests (CORS). I think that you should try to set to true the 'withCredentials' attribute on the underlying XHR object. See this question for more details:
Set-cookie in response not set for Angular2 post request
You could do something kind of hacky but I can't think of another way.
Cookie.setCookie(nameOfCookie, "", -1);
This would effectively delete the cookie on logout. I'd love to know if there was a better way though!
I also am not sure why you are getting any kind of page reload at all, I have yet to experience that on anything I've done yet, hopefully someone else will know.