angular2 with restful service - web-services

I am trying RESTful service with my Angular2 application.
It is working when I give local json url (/app/config.json). But it is not working when I try with below url.
URL : http://localhost:8082/RESTful_Jersey_JSON/rest/json/metallica/get
The above url will return json value as below,
{
"title": "SingerName",
"singer": "Singer123",
"id": "1",
"name": "Hero123"
}
app.module
#NgModule({
declarations: [ AppComponent,HeroListComponent,HeroDetailComponent],
imports: [BrowserModule,HttpModule,routing], bootstrap: [AppComponent]
})
HeroService.ts
getHeroes(){
var headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this._http.request("localhost:8082/RESTful_Jersey_JSON/rest/‌​json/metallica/…) .map((res:Response) => res.json());
}
HeroListComponent
#Component({
selector: 'my-hero-detail',
template: `
<h2>Hero List Component</h2>
<ul *ngFor="let hero of heroes">
{{hero}}
<li>{{hero.id}}</li>
<li>{{hero.name}}</li>
</ul>`
})
export class HeroListComponent implements OnInit {
heroes = [];
constructor(private _heroService:HeroService) {}
ngOnInit(){
this._heroService.getHeroes().
.subscribe(response =>this.heroes = response);
}
}

If what is returned is:
{ "title": "SingerName", "singer": "Singer123", "id": "1", "name": "Hero123" }
This means this is an Object, and not an array you can iterate through in your template.
So maybe change the variable name to just hero, since it's an object and not an array, but this is just a detail ;) Here I'll use heroes though:
ngOnInit(){
this._heroService.getHeroes().
subscribe(response => this.heroes = response);
}
Then you do not need to iterate the response, just display it like so:
<div>
{{heroes?.id}}
{{heroes?.name}}
</div>
And it's useful to use the safe navigation operator :)
Hope this helps!

as echonax said don't use relative paths and set header like this :
let options = new RequestOptions({ headers: headers });
return this._http.get("/assets/herolist.json", options ).map((res:Response) => res.json());

Related

How can I guard routes in Angular?

Currently, after logging in I'm able to get the JWT to the frontend. My app currently has a logging page as the landing page and as soon as the user logins the route checks for authentication to redirect to the guarded home path.
My first intuition was to send a boolean from the backend (Django) and use that to create a guard. But I keep seeing that seems to be better practice to handle this in the front end.
What I did was create an auth.service.ts and an auth.guard.ts. In the service, I try to retrieve the token from the browser and then verify that it hasn't expired. Then I call that method on the guard and return a boolean. Problem is that every time I look for the token in the local storage, I get back null.
Is there any better way to get achieve this?
auth.guard.ts
import { Injectable } from '#angular/core';
import {
ActivatedRouteSnapshot,
CanActivate,
Router,
RouterStateSnapshot,
UrlTree,
} from '#angular/router';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { AuthService } from './auth.service';
#Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
console.log(this.authService.isAuthenticated());
if(!this.authService.isAuthenticated()){
this.router.navigate(['/login']);
return false;
}
return true;
}
}
auth.service.ts
import { Injectable } from '#angular/core';
import { JwtHelperService } from '#auth0/angular-jwt';
#Injectable({
providedIn: 'root'
})
export class AuthService {
public jwtHelper: JwtHelperService = new JwtHelperService();
constructor() { }
isAuthenticated(){
const jwt = localStorage.getItem('token');
return !this.jwtHelper.isTokenExpired(jwt!);
}
}
app-routing.module.ts
...
import { AuthGuard } from './user/services/auth.guard'
const routes: Routes = [
{
path: '',
component: LandingComponent,
children: [
{ path: '', component: HomeComponent, canActivate: [AuthGuard],},
{ path: 'home', component: HomeComponent, canActivate: [AuthGuard],},
{
path: 'cohort-charts',
component: CohortChartsComponent,
children: [
{ path: 'selection', component: CohortSelectionComponent },
{ path: 'edit', component: CohortEditComponent },
{ path: '', redirectTo: 'selection', pathMatch: 'full' },
],
},
],
},
{
path: 'login',
component: LoginComponent,
},
{ path: '**', redirectTo: '' },
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
I guess that every time you look for the token in the local storage, you get back null because you aren't saving the token, or if you do, you are trying to store the token as object, not serialized (as string, stringifying it) , so it doesn't store, or when you get it, you aren't pasing it.
Any way, I guess that the best practice to manage the whole jwt/authentification section, would be with an interceptor:
And Interceptor is a service which intercepts all your http calls, and you cans set that it does something authomatically (for instance, magaging the jwt).
More info about how to Adding and updating headers and how to Use the interceptor for Intercepting requests and responses:
https://angular.io/guide/http#adding-and-updating-headers
https://angular.io/guide/http#intercepting-requests-and-responses
I leave you a glimpse of how you do it /what you need:
Provide the Angular Interceptor
app.module.ts:
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: HttpJwtAuthInterceptor,
multi: true,
},
{ provide: BASE_PATH, useValue: environment.apiUrl },
],
First, in your auth.service.ts, 2 methods to store/get the token
// STORE the token in localstore:
setToken(token:string){
// First, serialize it (but just if token is not string type).
const tokenString:string = JSON.stringify( token );
localStorage.setItem('token', tokenString);
}
// READ the token from localstorage and Deserialize
getToken(): string | null{
let token = localStorage.getItem( 'token' );
if( token !=null){
// You just need to parse if you serialized it inside setToken() method
token = JSON.parse(carItemsString);
}
return token;
}
Then, in your interceptor:
import { Injectable } from '#angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
} from '#angular/common/http';
import { AuthService } from '../_services/auth.service';
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const url="\yourAPI\endpoint";
// Get your token
cont myToken = this.authService.getToken();
// Add authorization header with token if available
if (myToken) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${myToken}`,
'Content-Type': 'application/json',
},
url,
});
}
…
return next.handle(request);
}

How to use PUT to update something in Vue using Django REST framework

I am new to Vue but have experience with Django. I am using this boilerplate from Github: https://github.com/gtalarico/django-vue-template
I really like the structure of that boilerplate because it is not overwelming at all and not a lot of code is written to succesfully interact with the back-end API of Django.
It has GET, POST & DELETE already pre-installed and connected to Django REST. So far so good. However I try to add a PUT method to it so I can update models. I try to follow the same structure but I can't get it to work.
My productService.js:
import api from '#/services/api'
export default {
fetchProducts() {
return api.get(`products/`)
.then(response => response.data)
},
postProduct(payload) {
return api.post(`products/`, payload)
.then(response => response.data)
},
deleteProduct(proId) {
return api.delete(`products/${proId}`)
.then(response => response.data)
},
updateProduct(proId) {
return api.put(`products/${proId}`)
.then(response => response.data)
}
}
The updateProduct is the new code I added.
Then in store --> products.js:
const actions = {
getProducts ({ commit }) {
productService.fetchProducts()
.then(products => {
commit('setProducts', products)
})
},
addProduct({ commit }, product) {
productService.postProduct(product)
.then(() => {
commit('addProduct', product)
})
},
deleteProduct( { commit }, proId) {
productService.deleteProduct(proId)
commit('deleteProduct', proId)
},
updateProduct( { commit }, proId) {
productService.updateProduct(proId)
commit('updateProduct', proId)
}
}
const mutations = {
setProducts (state, products) {
state.products = products
},
addProduct(state, product) {
state.products.push(product)
},
deleteProduct(state, proId) {
state.products = state.products.filter(obj => obj.pk !== proId)
},
updateProduct(state, proId) {
state.products = state.products.filter(obj => obj.pk !== proId)
}
}
Here again I added updateProduct.
Then in my Products.vue:
......
<b-tbody>
<b-tr v-for="(pro, index) in products" :key="index">
<b-td>{{ index }}</b-td>
<b-td variant="success">{{ pro.name }}</b-td>
<b-td>{{ pro.price }}</b-td>
<b-td>
<b-button variant="outline-primary" v-b-modal="'myModal' + index">Edit</b-button>
<b-modal v-bind:id="'myModal' + index" title="BootstrapVue">
<input type="text" :placeholder="pro.name" v-model="name">
<input type="number" :placeholder="pro.price" v-model="price">
<b-button type="submit" #click="updateProduct(pro.pk)" variant="outline-primary">Update</b-button>
</b-modal>
</b-td>
<b-td><b-button #click="deleteProduct(pro.pk)" variant="outline-primary">Delete</b-button></b-td>
</b-tr>
.....
<script>
import { mapState, mapActions } from 'vuex'
export default {
name: "Products",
data() {
return {
name: "",
price: "",
};
},
computed: mapState({
products: state => state.products.products
}),
methods: mapActions('products', [
'addProduct',
'deleteProduct',
'updateProduct'
]),
created() {
this.$store.dispatch('products/getProducts')
}
};
</script>
Everything works fine except the PUT action to update a product. I figured that you have to use the ID of a product to be able to edit it with PUT. So that's why I used the same snippet as DELETE. But right now I am still deleting it instead of editing.
I also used now placeholder to display the text of a product entry, which is also not the correct way.. I want to use the modal to edit a product entry and then update it.
Can someone point me in the right direction?

Angular2, jasmine: Component changes not seen in test spec function

I'm still writing tests for my Angular app. I've a test that modifies an Org object, saves the changes, and then proves that the changes have been kept. However, the test isn't seeing the changes.
My mock Org service that saves the changes:
#Injectable()
export class MockOrgService {
constructor() { }
public save(org: Org): Observable<Org> {
let savedOrg: Org = new Org(org);
savedOrg.address2 = 'Saved with id: ' + org.id;
return Observable.of(savedOrg);
}
}
My mock router:
beforeEach(async(() => {
routeStub = { data: Observable.of( { org: org1 } ), snapshot: {} } ;
TestBed.configureTestingModule({
imports: [ FormsModule, RouterTestingModule ],
providers : [
{ provide: DialogService, useClass: MockDialogService },
{ provide: GlobalsService, useClass: MockGlobalsService },
{ provide: OrgService, useClass: MockOrgService },
{ provide: ActivatedRoute, useValue: routeStub }
],
declarations: [ OrgDetailComponent ],
})
.compileComponents();
}));
My component function being tested:
private gotoParent(): void {
this.router.navigate(['../'], { relativeTo: this.route });
}
public save(): void {
this.error = null;
let that = this;
this.orgService
.save(that.org)
.subscribe(
(org: Org): void => {
that.org = org;
that.savedOrg = new Org(org);
that.gotoParent();
},
error => this.error = error
);
}
My test:
it('responds to the Save click by saving the Org and refilling the component', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
comp.org.id = 2;
comp.org.name = 'Another Org';
let elButton = fixture.debugElement.query(By.css('#save'));
elButton.nativeElement.click();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.error).toBeNull();
expect(comp.savedOrg.id).toEqual(2);
expect(comp.savedOrg.name).toEqual('Another Org');
expect(routeStub).toHaveBeenCalledWith(['../']);
});
});
When I use breakpoints I see that the OrgService.save() is called when click() is run, and that in the component save() function the that.savedOrg is being set. But when the test gets into the expect() functions comp.savedOrg is at its original value. It is as though there are two component instances.
FWIW, after setting, or not setting, my savedOrg my function then tries to route. I instead get an error:
Error: Expected a spy, but got Object({ data: ScalarObservable({ _isScalar: true, value: Object({ org: Org({ id: 2, [SNIP]
I'm not sure what I'm supposed to do to tell that the "goToParent" routing has been called.
Thanks in advance for help,
Jerome.
I figured out the "not seen in test spec function" issue. I am missing a line, right after the first whenStable(), which should be:
comp = fixture.componentInstance;
That makes everything sync OK. Now I must figure out how to make route testing work. That's another job.
Jerome.

Is it possible to mock custom Angular 2 Material SVG icons for unit tests?

In my app's root component, I am defining custom SVG icons for md-icon. When unit testing a component that displays the custom icon I get an error. It seems that the error is likely due to the fact that my root component is not being used/initialized in my child unit test.
Is there a way to mock or add these custom icons (or md-icon) when setting up the test module? I would simply define the icons in the component I am testing, but I know other components will need them also.
The error:
Uncaught Error: Error in ./CustomerComponent class CustomerComponent - inline template:34:19 caused by: __WEBPACK_IMPORTED_MODULE_4_rxjs_Observable__.Observable.throw is not a function
Full error:
Removing the custom icons from the template solves the error.
My template is using the custom icons like this:
<md-icon svgIcon="vip">vip</md-icon>
And the root component initializes the icons like this:
this.iconRegistry.addSvgIcon(
'vip',
this.sanitizer.bypassSecurityTrustResourceUrl('assets/icons/vip.svg') as string,
);
I set up the test component like this:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
],
providers: [
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: CUSTOMER,
company: COMPANY,
}),
},
},
},
{
provide: UtilityService,
useClass: UtilityServiceMock,
},
// etc...
],
declarations: [
CustomerComponent,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
})
.compileComponents();
}));
Versions
Angular 2.3.0
Material 2.0.0-beta.1
I was able to use the overrideModule method to stub MdIcon. The documentation is sparse but I was able to find a GitHub issue where Angular team members discuss how to override declarations. The idea is to remove the component from the MdIconModule so that we can declare our own mock icon component.
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestedComponent ],
imports: [
RouterTestingModule.withRoutes([]),
SharedModule,
],
})
.overrideModule(MdIconModule, {
remove: {
declarations: [MdIcon],
exports: [MdIcon]
},
add: {
declarations: [MockMdIconComponent],
exports: [MockMdIconComponent]
}
})
.compileComponents();
}));
The MockMdIconComponent is defined very simply
#Component({
selector: 'md-icon',
template: '<span></span>'
})
class MockMdIconComponent {
#Input() svgIcon: any;
#Input() fontSet: any;
#Input() fontIcon: any;
}
I used this approach because I am not importing the Material modules individually and I did not want my test to have to make Http calls to get the svg icons. The MockMdIconComponent could be declared in the testing module but I chose to declare/export it in the module override so that I could extract the object into a test helper.
Answering my own question:
After much trial/error with items like mocking the MdIconRegistry or using componentOverride() etc with no luck I no longer use a shared module within my tests.
Instead, I declare the MdIcon component directly in my testing module using a mock version of the class.
describe(`CustomerComponent`, () => {
let component: CustomerComponent;
let fixture: ComponentFixture<CustomerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
MdButtonModule,
],
providers: [
OVERLAY_PROVIDERS,
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: customer,
company: COMPANY,
}),
},
params: Observable.of({
customerId: customerId,
}),
},
},
],
declarations: [
CustomerComponent,
// Declare my own version of MdIcon here so that it is available for the CustomerComponent
MdIconMock,
],
});
fixture = TestBed.createComponent(CustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it(`should exist`, () => {
expect(component).toBeTruthy();
});
});
MdIconMock is simply a blank class that matches the selectors:
import { Component } from '#angular/core';
#Component({
template: '',
// tslint:disable-next-line
selector: 'md-icon, mat-icon',
})
// tslint:disable-next-line
export class MdIconMock {
}
Note: Due to TSLint rules that specify the prefix/format of class names/selectors I needed to disable TSLint for this mock.
This is a late answer. Just in case anyone come across this, there is an alternative other than OP's solution (which is nice as well):
Imports MaterialModule using forRoot()
TestBed.configureTestingModule({
declarations: [
TestComponent
],
imports: [SharedModule, MaterialModule.forRoot()],
providers: [{ provide: Router, useValue: routerStub }]
});
TestBed.compileComponents();
Get the injected MdIconRegistry and DomSanitizer
let iconRegistry = TestBed.get(MdIconRegistry);
let sanitizer = TestBed.get(DomSanitizer);
Configure them as you did in normal app
iconRegistry.addSvgIcon( 'some-icon',
sanitizer.bypassSecurityTrustResourceUrl('assets/img/some-icon.svg'));
building on #Chic's answer, since I have icons everywhere, i made a testbed patch:
import {TestBed} from '#angular/core/testing';
import {MatIconModule, MatIcon} from '#angular/material/icon';
import {Component, Input} from '#angular/core';
export function PatchTestBedMatIcons() {
const original = TestBed.configureTestingModule;
TestBed.configureTestingModule = (moduleDef) => {
return original(moduleDef)
.overrideModule(MatIconModule, {
remove: {
declarations: [MatIcon],
exports: [MatIcon]
},
add: {
declarations: [MockMatIconComponent],
exports: [MockMatIconComponent]
}
});
};
}
#Component({
selector: 'mat-icon',
template: '<span></span>'
})
class MockMatIconComponent {
#Input() svgIcon: any = null;
#Input() fontSet: any = null;
#Input() fontIcon: any = null;
}
then in your component test simply:
import {PatchTestBedMatIcons} from 'src/app/patchTestBedIcons';
PatchTestBedMatIcons();

angular2 - how to simulate error on http.post unit test

I m writing a Uni-test for a login Method with an HTTP.post call, like:
this.http.post( endpoint, creds, { headers: headers})
.map(res => res.json())
.subscribe(
data => this.onLoginComplete(data.access_token, credentials),
err => this.onHttpLoginFailed(err),
() => this.trace.debug(this.componentName, "Login completed.")
);
The problem is that i'm not able to simulate the error branch; everytime is called the onLoginComplete Method;
here is my test:
it("check that Atfer Login, console show an error ", inject(
[TraceService, Http, MockBackend, WsiEndpointService],
(traceService: TraceService, http: Http,
backend: MockBackend, wsiEndpoint: WsiEndpointService) => {
let tokenTest: number = 404 ;
let response: ResponseOptions = null {} // i think i have to modify this
let connection: any;
backend.connections.subscribe((c: any) => connection = c);
let authService: AuthService = new AuthService(http, Service1, Service2);
authenticationservice.login({ "username": "a", "password": "1" });
connection.mockRespond(new Response(response));
expect(ERROR);
}));
Thanks again to everyone.
First you need to override the XHRBackend class by the MockBackend one:
describe('HttpService Tests', () => {
beforeEachProviders(() => {
return [
HTTP_PROVIDERS,
provide(XHRBackend, { useClass: MockBackend }),
HttpService
];
});
(...)
});
Notice that HttpService is the service that uses the Http object and I want to test.
Then you need to inject the mockBackend and subscribe on its connections property. When a request is sent, the corresponding callback is called and you can specify the response elements like the body. The service will receive this response as the response of the call. So you'll be able to test your service method based on this.
Below I describe how to test the getItems method of the HttpService:
it('Should return a list of items', inject([XHRBackend, HttpService, Injector], (mockBackend, httpService, injector) => {
mockBackend.connections.subscribe(
(connection: MockConnection) => {
connection.mockRespond(new Response(
new ResponseOptions({
body: [ { id: '1', label: 'item1' }]
})));
});
httpService.getItems().subscribe(
items => {
expect(items).toEqual([ { id: '1', label: 'item1' }]);
});
});
});
Here is the code of getItems method of the HttpService:
#Injectable()
export class HttpService {
constructor(private http:Http) {
}
getItems(): Observable<any[]> {
return this.http.get('/items').map(res => res.json());
}
}
To simulate an error simply use the mockError method instead of the mockResponseone:
mockBackend.connections.subscribe(
(connection: MockConnection) => {
connection.mockError(new Error('some error'));
});
You can simulate an error like this:
connection.mockError(new Response(new ResponseOptions({
body: '',
status: 404,
})));
I created a small class
import {ResponseOptions, Response} from '#angular/http';
export class MockError extends Response implements Error {
name: any;
message: any;
constructor(status: number, body: string = '') {
super(new ResponseOptions({status, body}));
}
}
which can use like this
connection.mockError(new MockError(404));