Angular 6 - Display data from database with *ngFor - database

I'm trying to display datas from a database. I've found examples on the internet but it's not working at all
Error from Eclipse:
java.lang.IllegalArgumentException: id to load is required for loading
My html file:
<ul>
<li *ngFor="let questionnaire of questionnaires | async">{{questionnaire.name}}</li>
</ul>
My typeScript file:
import { Injectable } from '#angular/core';
import { Component, OnInit, Input } from '#angular/core';
import {QuestionnaireService} from '../services/questionnaire.service';
#Injectable()
#Component({
selector: 'app-qcm',
templateUrl: './qcm.component.html',
styleUrls: ['./qcm.component.sass']
})
export class QcmComponent implements OnInit {
#Input()
questionnaires :any = [];
constructor(private questionnaireService: QuestionnaireService) { }
ngOnInit() {
this.questionnaires = this.questionnaireService.getQuestionnaire(1);
}
}
My service:
import {environment} from '../../environments/environment';
import {Injectable} from '#angular/core';
import {HttpClient} from '#angular/common/http';
#Injectable({providedIn: 'root'})
export class QuestionnaireService {
user: any;
constructor(private http: HttpClient) {}
getQuestionnaire(id: number) {
return this.http.get<any>(`${environment.apiUrl}getQuestionnaire`);
}
}
Here is my webservice methode (spring hibernate)
#GET
#Path("/getQuestionnaire")
public Questionnaire getQuestionnaire(#QueryParam("id") Long id) throws Exception {
return FacadeBackOffice.getInstance().getQuestionnaireService().getQuestionnaire(id);
}

TODO list:
Check if the api call goes to the right URL.
Howto: Check the request and response on network tab.
questionnaires in your component is an Observable and not an array.
Why: HttpClient get returns an Observable, so you have two choices:
use the async pipe (you are doing it the right way)
subscribe to the observable to send the request and in the anonymous function passed to the subscribe assign questionnaires class variable with the response (or subset).
Improvement:
Makes no sense to populate questionnaires class variable two ways (Input of the component and result of a http get request). Leave only one option here.

you might be facing asynch data call issue.
Please try subscribing to your service and then storing the data to your array. e.g.
ngOnInit() {
this.questionnaireService.getQuestionnaire()
.subscribe(
(data) => {
this.questionnaires= data;
});
}

Try taking the async out of the html, and then putting a .subscribe() on the the service instead. Also, remove the #Input(), it looks like it is unneeded since your component's ngOnInit will be populating the array.
I added a console.log() so you know what is in your service call in case you might need something like this.questionarries = response.body.
html
<ul>
<li *ngFor="let questionnaire of questionnaires">{{questionnaire.name}}</li>
</ul>
component
import { Injectable } from '#angular/core';
import { Component, OnInit, Input } from '#angular/core';
import {QuestionnaireService} from '../services/questionnaire.service';
#Injectable()
#Component({
selector: 'app-qcm',
templateUrl: './qcm.component.html',
styleUrls: ['./qcm.component.sass']
})
export class QcmComponent implements OnInit {
questionnaires :any = [];
constructor(private questionnaireService: QuestionnaireService) { }
ngOnInit() {
this.questionnaireService.getQuestionnaire().subscribe(
response => {
this.questionnaires = response;
console.log(this.questionnaries);
}
);
}
}

Related

Problem passing data from one component to another component using services in Angular 8

Am working on a Single Page Application CRUD based API whereby am passing
data from the frontend to the backend using JWT which works fine. On the backend am saving the data in a database and later fetching all the data and return as a response to the frontend which works fine.
On the frontend, I fetch the data in the response and pass to another component through a Service. Basically I pass the data from create component to show component via a service called Shared service. The data am passing (response from the backend is an array of Javascript objects).
the problem is that the data is not reaching the show component, since I want after it reaches the show component I want to loop through the array of objects and show them on the view (show.component.html) as a table
Please assist?
Create Component
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { SharedService } from 'src/app/Services/shared.service';
#Component({
selector: 'app-create',
templateUrl: './create.component.html',
styleUrls: ['./create.component.css']
})
export class CreateComponent implements OnInit {
public form = {
sponsorFirstName: null,
sponsorSurName: null,
sponsorEmail: null,
sponsorPhone: null,
nationality: null,
childFirstName: null,
childAge: null,
childSurName: null,
childGender: null
};
public error = null;
constructor(
private router: Router,
private Shared : SharedService) { }
onSubmit(){
this.Auth.submitFormData(this.form).subscribe(
data => this.handleResponse(data),
error => this.handleError(error)
);
}
handleResponse(data){
//console.log(data.data);
//pass the data to the shared service
this.Shared.createData(data.data);
//redirect to show-data component whereby I show the data in a table
this.router.navigateByUrl('/show-data');
}
handleError(error){
this.error = error.error.errors;
}
ngOnInit() {
}
}
Shared service
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
import { TokenService } from './token.service';
#Injectable({
providedIn: 'root'
})
export class SharedService {
private userData = new BehaviorSubject;
checkUser$ = this.userData.asObservable();
createData(data:any){
this.userData.next(data);
}
constructor(private Token : TokenService) { }
}
Show.component.ts file
import { Component, OnInit } from '#angular/core';
import { SharedService } from 'src/app/Services/shared.service';
#Component({
selector: 'app-show',
templateUrl: './show.component.html',
styleUrls: ['./show.component.css']
})
export class ShowComponent implements OnInit {
public userData;
constructor(
private Shared : SharedService,
) { }
ngOnInit() {
this.Shared.checkUser$.subscribe(message => this.userData = message);
}
}
Seems like a race condition between the service and your component, you should try and instantiate userData with a ReplaySubject(1) like so:
private userData = new ReplaySubject(1);
This way your subject will have a buffer and even if you late subscribe to it you'll have the value.
ReplaySubject
A Subject that "replays" or emits old values to new
subscribers

HTTP PUT Request Returns Empty From Angular 2 Service

I'm calling data from a service. I have to pass in an object with properties to retrieve the data. From what I can tell, I'm passing it in correctly and not receiving any errors in console, but the object that I assign to the data is empty. I'm including my Service and Component where I'm outputting the data.
I call the exact same endpoint from an Angular1 App with the same "payload" values and get my data object returned. I'm sure I'm missing something simple. Still getting used to Angular 2.
SERVICE
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable ()
export class DataService {
private qrDataUrl = 'http://my.data.url/endpoint';
private payload = {
DeliveryTypeId: 0,
PickupLocationid: 0,
PaymentTypeId: 0,
Items: ['XXXX'],
ApplicationToken:'123MYTOKEN456',
DateOfBirth: "1961-01-01T00:00:00.000Z"
};
constructor(private http: Http){ }
getQr():Observable<any>{
return this.http.put(this.qrDataUrl, this.payload)
.map((res:Response) => res.json());
}
}
COMPONENT
import { Component, OnInit } from '#angular/core';
import { DataService } from '../shared/dataService';
#Component({
selector: 'my-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
providers: [DataService]
})
export class HomeComponent implements OnInit {
qrData = { };
constructor(private dataService: DataService) {
// Do stuff
}
getData(){
this.dataService.getQr().subscribe(data => this.qrData = data);
console.log(this.qrData); //This logs an empty object
}
ngOnInit() {
console.log('Hello Home');
this.getData();
}
}
Asynchronous operation doesn't work synchronous way. You have to wait until the ajax gets completed. You can only see a data inside subscription of your getQr function once the service call resolved.
getData(){
this.dataService.getQr().subscribe(data => {
this.qrData = data;
console.log(this.qrData); //This logs an empty object
});
}

Angular 2 Communicating with <router-outlet> components

I have a search bar in a header component.
Beneath that, I have a "router-outlet" in that same component.
The search bar (input txtfield), once enter is pressed, needs to send the search string (event.target.value) to the component that resides within the router-outlet beneath it so that it can run a method to return the results.
I have no clue what the best way is to achieve this.
UPDATED with code..
app.component.html:
<div class="white-container">
<input name="searchStr" [(ngModel)]="searchStr" (keyup.enter)="searchCourse($event)">
</div>
<router-outlet></router-outlet>
app.component.ts:
import { Component, OnInit } from '#angular/core';
import { CourseService } from './services/courses.service';
import { Course } from './Course';
#Component({
selector: 'my-app',
templateUrl: 'app.component.html',
providers: [CourseService]
})
export class AppComponent implements OnInit {
constructor(private _courseService: CourseService) {
}
searchCourse(event) {
// the user search string here...
}
}
/course-listings/course-listings.component.ts:
import { Component, OnInit } from '#angular/core';
import { CourseService } from './services/courses.service';
import { Course } from './Course';
#Component({
selector: 'app-course-listings',
templateUrl: './course-listings.component.html',
styleUrls: ['./course-listings.component.css'],
providers: [CourseService]
})
export class AppComponent implements OnInit {
course: Course[];
constructor(private _courseService: CourseService) {
}
searchCourse(evt) {
// This works once it's fired...
this._courseService.findCourse(evt)
.subscribe(courses => {
this.course = courses;
});
}
}
/services/courses.service.ts:
import {Injectable} from '#angular/core';
import {Http} from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class CourseService {
constructor(private _http:Http) {
}
getCourses(search) {
return this._http.get('/api/v1/courses/'+search)
.map(res => res.json());
}
}
FIX FOUND
Günter Zöchbauer was correct. I used a service w/ subscribe and observables to do it. Thanks.
An event.subscriber would be required in the constructor to pass to router-outlet.
Similar to the answer in this Angular 2 router event listener.
So, once the click is done, the subscriber event will be executed based on on the navigationend event, then the value can be accessed.

Angular2 shared observable

Doing some experimentation with Angular2 and curious about how to solve a situation where a service exposes a shared observable. Where one component is responsible for getting the data and another is responsible for displaying the data. Here is some code:
The common HttpService responsible for getting the data
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class Service {
subject = new Subject<string[]>;
observable$ = this.subject.asObservable();
constructor(private http: Http) {}
observable: Observable<string[]>;
get(link: string): Observable<string[]> {
this.observable$ = this.http.get('myapi.com')
.map((res: Response) => this.subject.next(res.json())
.catch(this.handleError);
return this.observable$;
}
/**
* Handle HTTP error
*/
private handleError (error: any) {
// In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
}
The GetComponent responsible for getting the data
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Service } from '../shared/index';
#Component({
moduleId: module.id,
selector: 'get',
templateUrl: 'get.component.html',
providers: [Service],
})
export class GetComponent {
constructor(public service: Service) {}
submit() {
this.service.get(this.url).subscribe();
}
}
The DisplayComponent responsible for displaying the data
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
#Component({
moduleId: module.id,
selector: 'display',
templateUrl: 'display.component.html'
})
export class DisplayComponent {
subscription: Subscription;
constructor(public service: Service) {}
ngOnInit() {
// the observable is undefined :(
this.subscription = this.service.observable$.subscribe(data => {
console.log(data);
},
error => {
// this never gets reached :(
})
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
}
This design works ok except the error handling does not work for the DisplayComponent. Also the map function in the HttpService doesn't seem quite right this.subject.next(res.json()
Also it doesn't seem quite right that the GetComponent has to "subscribe" like this this.service.get(this.url).subscribe();
What is the proper way to design this sort of thing? How can I get the DisplayComponent to observe errors thrown by the HttpComponent
Your code has several problems where Service.$observable is modified several times:
observable$ = this.subject.asObservable();
this.observable$ = this.http.get('myapi.com') .... // remove this line
The right way to do this:
Get Component calls Service to get data. After data is loaded, Get Component emits a Data Ready event with the data. Display Component, and other components that use this data, listen to DataReady event and update data when the event is emitted.
Code to explain my answer:
#Injectable()
export class GetService {
/// subject
subject = new Subject<string[]>()
/// the observable
observable = this.subject.asObservable()
constructor(private $http: Http) {
}
/// get data
getData() {
// not override observable here
this.$http.get("api.com")
.map(response => response.json()) // map the data
.subscribe((data: string[]) => this.subject.next(data), // emit data event
error => this.subject.error(error)) // emit error event
}
}

How do I ensure the data has arrived before the template tries to render it?

I made an app with angular-cli, Immutable and Redux. I followed instructions from this article which does not describe how to get data. My app needs to initialize the Redux store with data from an asynchronous http call.
I have a component for listing the data which has a template. This component gets its data from the store which depends on a service which makes the http call. The http call works but the app throws an exception that indicates the listing component is trying to get the data before it has arrived.
My repository is here
A demo of the app is here
Error message:
main.js:21 TypeError: Cannot read property 'getState' of undefined
at a.get [as objections] (https://dancancro.github.io/bernierebuttals/main.js:18:22268)
at a._View_a0.detectChangesInternal (a.template.js:189:37)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectViewChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13774)
at a._View_a_Host0.detectChangesInternal (a.template.js:34:8)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectContentChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13588)
at a.detectChangesInternal (https://dancancro.github.io/bernierebuttals/main.js:32:13345)
at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
at a.detectViewChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13774)
Here are some relevant parts of the code: (I'm working on this. The repository contains the current code)
list.component.html
...
<ul id="objection-list" [sortablejs]="store.objections" [sortablejsOptions]="options" (update)="setTouched()">
<li *ngFor="let objection of store.objections">
<list-objection
[objection]="objection"
[editable]="editable"
(onEdit)="setTouched()"
(onReordered)="setReordered(objection)"
></list-objection>
</li>
</ul>
...
list.component.ts
import { Component, OnInit, ContentChildren, QueryList } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { SortablejsOptions, SORTABLEJS_DIRECTIVES } from 'angular-sortablejs';
import Immutable = require('immutable');
import { ObjectionComponent } from './objection/objection.component';
import { ObjectionModel } from '../objection';
import { ObjectionStore } from '../objection-store';
import { DataService } from '../data.service';
import { addObjection } from '../actions';
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [ObjectionStore, DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
private sub: any;
editable: boolean = false;
touched: boolean = false;
expanded: boolean = false;
options: SortablejsOptions = {
disabled: false
};
objectionID: number;
constructor(
private store: ObjectionStore,
private route: ActivatedRoute) {}
...
}
objection-store.ts
import { Injectable } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import Immutable = require('immutable');
import { createStore } from 'redux';
import { ObjectionAction } from './actions';
import { reducer } from './reducer';
import { ObjectionModel } from './objection';
import { DataService } from './data.service';
#Injectable()
export class ObjectionStore {
private sub: any;
store: any;
constructor(
private dataService: DataService) {
this.store = createStore(reducer, Immutable.List<ObjectionModel>(objections.json()));
});
}
data.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { ObjectionModel } from './objection';
import { Area } from './area';
let objectionsPromise;
#Injectable()
export class DataService {
result: Object;
combined: any;
error: Object;
getUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec?json=1';
postUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec';
static getObjection(objections: any[], id: number): ObjectionModel {
return objections.filter(function(objection) {
return objection.id === id
})[0];
}
constructor(private http: Http) {
objectionsPromise = this.http.get(this.getUrl).toPromise();
}
Basic Answer
To answer the immediate question, getting the objections from the server and putting them in a template can be simply done like this:
Use async pipe to bind an observable directly into the template. Async pipe 'unboxes' observables (promises too) and updates your template when they change.
<ul id="objection-list"
[sortablejs]="store.objections"
[sortablejsOptions]="options"
(update)="setTouched()">
<li *ngFor="let objection of objections | async">
<list-objection
[objection]="objection"
[editable]="editable"
(onEdit)="setTouched()"
(onReordered)="setReordered(objection)">
</list-objection>
</li>
</ul>
Use DataService to initialize this observable when your component is initialized:
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [ObjectionStore, DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
// ...
private objections: Observable<ObjectionModel[]>;
constructor(
private dataService: DataService,
private route: ActivatedRoute) {}
ngOnInit() {
this.objections = this.dataService.getObjections();
}
// ...
Fix up your data service:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { ObjectionModel } from './objection';
#Injectable()
export class DataService {
result: Object;
combined: any;
error: Object;
getUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec?json=1';
postUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec';
getObjections(private http: Http): Observable<ObjectionModel[]> {
return this.http.get(this.getUrl) // returns an observable of the response
.map(response => response.json()); // transforms it into an observable of ObjectionModels
}
}
Notes on Redux
Note that this has all been done without Redux.
In general, I like Redux and I use it a lot. However in your example you seem to be doing a couple of unorthodox things:
You are creating a store in your ObservableStore service - this suggests to me that you are planning on having several stores in your app. One of the main principles of Redux is global immutable state, meaning that there is normally only one Redux store in an application.
You seem to by trying to fetch the initial data set from the server and then creating your store when the response has come back. It's not generally a good idea to couple the store creation to an HTTP request like this. Instead I recommend creating an empty store when you initialize your app, and then updating it via reducer when the HTTP request comes back.
You can do raw Redux in Angular 2, but you may find it a little frustrating getting it to work with Angular's observable-heavy APIs. Fortunately people (including me) have done this work for you in the form of Observable-oriented redux libraries like ng2-redux and ngrx/store
If you were to use ng2-redux, things would look more like this:
Top-level app component: build your store and initialize it:
import { NgRedux } from 'ng2-redux';
import { rootReducer } from './reducers';
#Component({ /* ... */ })
class App {
constructor(private ngRedux: NgRedux<any>) {
this.ngRedux.configureStore(rootReducer, {});
}
}
List component: bind your template to a selector from the store's current data. Also trigger a data fetch on initialization.
import { NgRedux, select } from 'ng2-redux';
#Component({
moduleId: module.id,
selector: 'app-list',
templateUrl: 'list.component.html',
styleUrls: ['list.component.css'],
providers: [DataService],
directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {
// ...
// Magic selector from ng2-redux that makes an observable out
// of the 'objections' property of your store.
#select('objections') objections: Observable<ObjectionModel[]>;
constructor(
private ngRedux: NgRedux<any>,
private dataService: DataService) {}
ngOnInit() {
this.subscription = this.dataService.getObjections()
.subscribe(objections => this.ngRedux.dispatch({
type: FETCH_OBJECTIONS_OK,
payload: objections
},
error => this.ngRedux.dispatch({
type: FETCH_OBJECTIONS_ERROR,
error: error
});
)
}
}
OK... so how does the data actually get into the store? Via the reducer. Remember in redux store state can only be changed from a reducer.
export function objectionReducer(state = [], action) {
switch(action.type) {
case FETCH_OBJECTIONS_OK: return [ ...action.payload ];
case ADD_OBJECTION: return [ ...state, action.payload ];
// etc.
}
return state;
}
We can also track errors in a reducer too if we want, how you want to structure this is up to you.
export function errorReducer(state = {}, action) {
switch(action.type) {
case FETCH_OBJECTIONS_ERROR: return { objectionFetch: action.error }
}
}
Since we have one store, we modularize the reducers instead and compose them together:
import { combineReducers } from 'redux';
import { objectionReducer } from './objection.reducer';
import { errorReducer } from './error.reducer';
export const rootReducer = combineReducers({
objections: objectionReducer,
error: errorReducer
});
More to Learn/Disclosure
Disclosure: I am one of the authors of Ng2-Redux. However Ngrx/Store is also a great alternative for doing redux with Ng2, and while the implementation is different using it is very similar to what I have described above.
I and my colleagues also maintain a number of training resources on Angular2 and on Redux, which I will provide below:
A good Angular2-specific intro to redux: http://angular-2-training-book.rangle.io/handout/redux/
An intro to Observables: http://angular-2-training-book.rangle.io/handout/observables/
Ng2-Redux documentation: https://github.com/angular-redux/ng2-redux/blob/master/README.md
Your data service is wrong. you are missing the point of the promise/observable.
you should read angular documentation about http client. at least read this part:
https://angular.io/docs/ts/latest/guide/server-communication.html#!#promises
No http call in the constructor!
this is more like it:
getHeroes (): Promise<Hero[]> {
return this.http.get(this.heroesUrl)
.toPromise()
.catch(this.handleError);
}
After you get use to that, I really recommend you read some about Observables. much cleaner and advanced.

Resources