angular not displaying object attributes - django

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
}
]

Related

How to create and update products to a REST API from Angular

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.

How to load json api data to html template in angular?

Can anyone help me with a simple question(I guess). I'm trying to display a json file in a table through Django into an Angular front end app. I can view the data by console logging it, however I can't seem to get the data into the webpage.
I know how to display the table in HTML. The specific problem is that the object which is fetched from the API does not appear in the HTML.
component.ts
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild,
ViewEncapsulation } from '#angular/core';
import ApexChart from 'apexcharts'
import { ApexOptions } from 'ng-apexcharts';
import { FinanceService } from 'app/modules/admin/dashboards/finance/finance.service';
#Component({
selector: 'app-finance',
templateUrl: './finance.component.html',
styleUrls: ['./finance.component.scss'],
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FinanceComponent implements OnInit {
_stockData: any;
_portData: any;
_effData: any;
_calData: any;
_labels: any;
_table: any;
constructor(
private _financeService: FinanceService,
){
}
ngOnInit(): void {
this._financeService.getFinanceData()
.subscribe((res: any) => {
console.log(res);
this._stockData = res.stocks;
this._portData = res.port;
this._effData = res.eff;
this._calData = res.cal;
this._labels = res.labels;
this._table = res.table;
console.log(res.table);
this._prepareChartData();
},
(response) => {
// Re-enable the form
});
}
Service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { catchError, switchMap } from 'rxjs/operators';
import { BehaviorSubject, of, Observable } from 'rxjs';
import { environment } from 'environments/environment';
#Injectable({
providedIn: 'root'
})
export class FinanceService {
/**
* Constructor
* #param {HttpClient} _httpClient
*/
constructor(private _httpClient: HttpClient){
}
// -----------------------------------------------------------------------------------------------------
// # Public methods
// -----------------------------------------------------------------------------------------------------
/**
* get Finance Data
*/
getFinanceData(): Observable<any>{
return this._httpClient.get(environment.baseURL + 'apiurls/', {}).pipe(
switchMap((response: any) => {
return of(response);
})
);
}
}
template.html
<div>
<p>{{_table.Percentage}}</p>
</div>
json (as presented by the django API)
{
"stocks":[],
"eff":[],
"port":[],
"cal":[],
"labels":[],
"table":{
"Percentage":{
"AD.AS":16.1,
"ASML.AS":15.67,
"AAPL":68.23
},
"Budget":{
"AD.AS":241.5,
"ASML.AS":235.05,
"AAPL":1023.45
},
"Closing":{
"AD.AS":25.22,
"ASML.AS":314.3,
"AAPL":129.04
},
"Number of stocks to buy":{
"AD.AS":10.0,
"ASML.AS":1.0,
"AAPL":8.0
},
"final":{
"AD.AS":252.0,
"ASML.AS":314.0,
"AAPL":1032.0
},
"final allocation":{
"AD.AS":15.77,
"ASML.AS":19.65,
"AAPL":64.58
},
"Diff":{
"AD.AS":-0.33,
"ASML.AS":3.98,
"AAPL":-3.65
}
}
}
To display the json data on html you may use json pipe
<pre>
{{ _table.Percentage | json }}
</pre>

Angular 8 - problem with GET request for a json file

Please help, i'm trying to lift up my django web page with REST API and use Angular FrontEnd where I am beginning. I have followed some tutorials on how to consume REST api and somehow I'm making a mistake there. The browser shows no errors when requesting the content but it is not coming. I appreciate every bit of help.....
here we go usluga-list.component.ts:
import { Component, OnInit } from '#angular/core';
import { Observable } from "rxjs";
import { Usluga } from "../models/usluga";
import { UslugaService } from "../services/usluga.service";
#Component({
selector: 'app-usluga-list',
templateUrl: './usluga-list.component.html',
styleUrls: ['./usluga-list.component.css']
})
export class UslugaListComponent implements OnInit {
uslugi: Observable<Usluga[]>;
constructor(private uslugaService: UslugaService) { }
ngOnInit() {
this.loadUslugiData();
}
loadUslugiData(){
this.uslugi = this.uslugaService.getAllUslugi();
then i have usluga.service.ts:
import { Injectable } from '#angular/core';
import { HttpClient } from "#angular/common/http";
import { Observable } from "rxjs";
import { Usluga } from "../models/usluga";
#Injectable({
providedIn: 'root'
})
export class UslugaService {
private endpoint ='http://localhost:8000/uslugi/';
constructor(private http: HttpClient) { }
getAllUslugi(): Observable<any>{
return this.http.get(this.endpoint)
}
getUsluga(id: number): Observable<any> {
return this.http.get(this.endpoint + id);
}
}
Then I have app-routing.ts:
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { UslugaListComponent } from './usluga-list/usluga-list.component';
const routes: Routes = [
{path: '', redirectTo: 'uslugi', pathMatch: 'full'},
{path: 'uslugi', component: UslugaListComponent}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
It is odd as i get the json via localhost:8000/uslugi and it must be something wrong in the Angular:
[{"id": 1, "title": "Wizyta", "text": "Wizyta", "price": "10.99", "slug": "wizyta-p", "category": "psyc", "lekarz_id": 1}, {"id": 2, "title": "Wizyta d", "text": "Wizyta dia to...", "price": "199.30", "slug": "wiz", "category": "sek", "lekarz_id": 1}]
In order for the http call to be made you need to subscribe to it.
In your component:
this.uslugi = this.uslugaService.getAllUslugi();
// if you just want to test that it works:
this.uslugi.subscribe();
The proper way is to unsubscribe, there are many ways of doing this. This is one way that only require 'rxjs' and operators. So what you do is essentially like this:
#Component({
selector: 'app-usluga-list',
templateUrl: './usluga-list.component.html',
styleUrls: ['./usluga-list.component.css']
})
export class UslugaListComponent implements OnInit, OnDestroy {
uslugi: Observable<Usluga[]>;
unsubscribe = new Subject();
constructor(private uslugaService: UslugaService) { }
ngOnInit() {
this.loadUslugiData();
}
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
loadUslugiData(){
this.uslugi = this.uslugaService.getAllUslugi();
this.uslugi.pipe(takeUntil(this.unsubscribe)).subscribe();
}
Then that should make the http call for you
The observable variable (that stores the object from the api) name must be the same as the name in the html template, alike (flightss):
#Component({
selector: 'app-flight-list',
templateUrl: './usluga-list.component.html',
styleUrls: ['./usluga-list.component.css']
})
export class UslugaListComponent implements OnInit {
flightss: Observable<Usluga[]>;
constructor(private uslugaService: UslugaService) { }
ngOnInit() {
this.loadUslugisData();
}
loadUslugisData() {
this.flightss = this.uslugaService.getAllUslugi();
}
}
and in the template:
<tr *ngFor="let flight of flightss | async; let i=index">
<td class="text-center">{{flight.id}}</td>
<td>{{flight.title}}</td>
<td>{{flight.text}}</td>
<td>{{flight.price}}</td>
<td>{{flight.slug}}</td>
<td>{{flight.category}}</td>
<td class="td-actions text-right">
<a type="button" class="btn btn-success btn-just-icon btn-sm "
style="margin-left:10px;">
<i class="material-icons">Edit</i>
</a>
<button type="button" rel="tooltip" class="btn btn-danger btn-just-icon btn-sm" data-original-title="" title="" style="margin-left:10px;">
<i class="material-icons">Delete</i>
</button>
</td>
</tr>

angular save() method not working hero tutorial

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)
}

Angular2 - Testing ngOninit in Components

I have a listing component with following code:
///<reference path="../../node_modules/angular2/typings/browser.d.ts"/>
import { Component, OnInit } from 'angular2/core';
import { ROUTER_DIRECTIVES } from 'angular2/router';
import { Employee } from '../models/employee';
import { EmployeeListServiceComponent } from '../services/employee-list-service.component';
#Component({
selector: 'employee-list',
template: `
<ul class="employees">
<li *ngFor="#employee of employees">
<a [routerLink]="['EmployeeDetail', {id: employee.id}]">
<span class="badge">{{employee.id}}</span>
{{employee.name}}
</a>
</li>
</ul>
`,
directives: [ROUTER_DIRECTIVES],
providers: [EmployeeListServiceComponent]
})
export class EmployeeListComponent implements OnInit {
public employees: Employee[];
public errorMessage: string;
constructor(
private _listingService: EmployeeListServiceComponent
){}
ngOnInit() {
this._listingService.getEmployees().subscribe(
employees => this.employees = employees,
error => this.errorMessage = <any>error
);
}
}
I wish to write unit tests for the ngOninit hook. I have written following test:
/// <reference path="../../typings/main/ambient/jasmine/jasmine.d.ts" />
import {
it,
describe,
expect,
TestComponentBuilder,
injectAsync,
setBaseTestProviders,
beforeEachProviders,
} from "angular2/testing";
import { Component, provide, ApplicationRef, OnInit } from "angular2/core";
import {
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
} from "angular2/platform/testing/browser";
import {
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
ROUTER_PRIMARY_COMPONENT,
APP_BASE_HREF
} from 'angular2/router';
import {XHRBackend, HTTP_PROVIDERS} from "angular2/http";
import { MockApplicationRef } from 'angular2/src/mock/mock_application_ref';
import {MockBackend } from "angular2/src/http/backends/mock_backend";
import {Observable} from 'rxjs/Rx';
import 'rxjs/Rx';
import { Employee } from '../models/employee';
import { EmployeeListComponent } from './list.component';
import { EmployeeListServiceComponent } from '../services/employee-list-service.component';
class MockEmployeeListServiceComponent {
getEmployees () {
return Observable.of([
{
"id": 1,
"name": "Abhinav Mishra"
}
]);
}
}
#Component({
template: '<employee-list></employee-list>',
directives: [EmployeeListComponent],
providers: [MockEmployeeListServiceComponent]
})
class TestMyList {}
describe('Employee List Tests', () => {
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);
beforeEachProviders(() => {
return [
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(EmployeeListServiceComponent, {useClass: MockEmployeeListServiceComponent}),
provide(XHRBackend, {useClass: MockBackend}),
provide(APP_BASE_HREF, {useValue: '/'}),
provide(ROUTER_PRIMARY_COMPONENT, {useValue: EmployeeListComponent}),
provide(ApplicationRef, {useClass: MockApplicationRef})
]
});
it('Should be true',
injectAsync([TestComponentBuilder], (tcb) => {
return tcb
.createAsync(TestMyList)
.then((fixture) => {
fixture.detectChanges();
var compiled = fixture.debugElement.nativeElement;
console.log(compiled.innerHTML);
expect(true).toBe(true);
});
})
);
});
However, the output of console.log in the test is an empty ul tag as follows:
'<employee-list>
<ul class="employees">
<!--template bindings={}-->
</ul>
</employee-list>'
Can anyone suggest me the proper way of writing unit tests for component hooks?
SOLUTION
Mock the http request in the injectAsync block as follows:
backend.connections.subscribe(
(connection:MockConnection) => {
var options = new ResponseOptions({
body: [
{
"id": 1,
"name": "Abhinav Mishra"
}
]
});
var response = new Response(options);
connection.mockRespond(response);
}
);
However now i am getting another error as follows:
Failed: EXCEPTION: Component "EmployeeListComponent" has no route config. in [['EmployeeDetail', {id: employee.id}] in EmployeeListComponent#3:7]
ORIGINAL EXCEPTION: Component "EmployeeListComponent" has no route config.
ORIGINAL STACKTRACE:
Error: Component "EmployeeListComponent" has no route config.
If you call async code in ngOnInit() you can't assume it is completed when console.log(...) is executed. this.employees is only set when the callback you passed to subscribe(...) gets called after the response arrived.
If you use MockBackend you can control the response and after the response was passed you have to run fixture.detectChanges() again to make the component re-render with the updated data, then you can read innerHTML and expect it to contain the rendered content.