I have a list of Question , every object of type Question has a 1:n relationship with objects of type Answer (so every Question object has a list of Answer objects). I'm trying to display on the browser, using Angular material, all the Question objects with their answers after a click event, but when I try to do so, the Question objects are displayed without their answers. After some researches I found out that even if the Answer and Question objects are correcty "connected" and stored in the database, the list of answers results to be undefined as shown in the following console:
{id: 10, question: 'Question1', questionsForUser: Array(0), answers: undefined}
Answers: homepage.component.ts:32
undefined homepage.component.ts:33
How can I deal with this problem?
Here is the relationship between Question and Answer:
homepage.component is the component in which the following click event occurs:
<div class="button">
<button mat-button (click)="getQuestions()" routerLink="questions/getAllQuestions" routerLinkActive="active">Show</button>
</div>
homepage.component.ts:
export class HomepageComponent implements OnInit {
longText = `...`;
public questions: Question[] = [];
constructor(private questionService: QuestionService, private shared: SharedService) { }
ngOnInit(): void {
this.shared.castQuestions.subscribe(questions=>this.questions=questions);
}
public getQuestions():void{
this.questionService.getQuestions().subscribe(
(response: Question[]) => {
this.questions =response;
this.shared.showQuestions(this.questions);
console.log(response);
for(let i=0; i<response.length; i++){
this.questions[i].answers=response[i].answers;
console.log(response[i]);
console.log("Answers:");
console.log(response[i].answers);
}
},
(error: HttpErrorResponse) => {
alert(error.message);
}
);
}
}
After the click event, thanks to Angular routing, the tool.component code should be executed.
tool.component.html:
<table mat-table [dataSource]="questions" class="mat-elevation-z8">
<!-- Question Column -->
<ng-container matColumnDef="question">
<th mat-header-cell *matHeaderCellDef> Question </th>
<td mat-cell *matCellDef="let question"> {{question.question}} </td>
</ng-container>
<!-- Answers Column -->
<ng-container matColumnDef="answers">
<th mat-header-cell *matHeaderCellDef> Answers </th>
<td mat-cell *matCellDef="let question"> {{question.answers}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
tool.component.ts:
export class ToolComponent implements OnInit {
public questions: Question[] = [];
displayedColumns = ['question', 'answers'];
constructor(private shared: SharedService) { }
ngOnInit(): void {
this.shared.castQuestions.subscribe(questions=>this.questions=questions);
}
}
shared.service.ts:
@Injectable({
providedIn: 'root'
})
export class SharedService {
private questions= new BehaviorSubject<Array<Question>>([]);
castQuestions = this.questions.asObservable();
showQuestions(data: Question[]){
this.questions.next(data);
}
}
console.log(response):
question.service.ts:
@Injectable({
providedIn: 'root'
})
public getQuestions(): Observable<Question[]> {
return this.http.get<Question[]>('http://localhost:8080/questions/getAllQuestions');
}
}
Back/api with which I create the response:
Question entity:
@Getter
@Setter
@EqualsAndHashCode
@ToString
@Entity
@Table(name = "question", schema = "purchase")
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private int id;
@Basic
@Column(name = "question", nullable = true, length = 1000)
private String question;
@OneToMany(mappedBy = "question", cascade = CascadeType.MERGE)
private List<QuestionForUser> questionsForUser;
@OneToMany(mappedBy = "question", cascade = CascadeType.MERGE)
@JsonIgnore
private List<Answer> answers;
}
Answer entity:
@Getter
@Setter
@EqualsAndHashCode
@ToString
@Entity
@Table(name = "answer", schema = "purchase")
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private int id;
@Basic
@Column(name = "answer", nullable = true, length = 1000)
private String answer;
@ManyToOne
@JoinColumn(name = "question")
private Question question;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
QuestionRepository:
@Repository
public interface QuestionRepository extends JpaRepository<Question, Integer> {
List<Question> findByQuestionContaining(String question);
boolean existsByQuestion(String question);
}
QuestionService:
@Service
public class QuestionService {
@Autowired
private QuestionRepository questionRepository;
@Transactional(readOnly = true)
public List<Question> getAllQuestions(){
return questionRepository.findAll();
}
}
QuestionController:
@GetMapping("/getAllQuestions")
public List<Question> getAll(){
List<Question> ques = questionService.getAllQuestions();
for(Question q:ques){
System.out.println(q.getQuestion());
if(q.getAnswers()!=null){
System.out.println("The answers are: "+q.getAnswers().size());
}
}
return questionService.getAllQuestions();
}
As suggested, I tried to add a test to getAll() in QuestionController. To make the test return a string, I temporarily changed the method getAll() in this way:
@GetMapping("/getAllQuestions")//funziona
public String getAll(){
List<Question> result = questionService.getAllQuestions();
String answer = result.get(0).getAnswers().get(0).getAnswer();
return answer;
}
Then, I wrote the following test:
class QuestionControllerTest {
@Test
void getAll() {
QuestionController controller = new QuestionController(); //Arrange
String response = controller.getAll(); //Act
assertEquals("a1", response); //Assert
}
}
The first answer of the first question should be a1, but when I execute the test on IntelliJ I have the following result:
java.lang.NullPointerException: Cannot invoke "com.ptoject.demo.services.QuestionService.getAllQuestions()" because "this.questionService" is null
at com.ptoject.demo.controller.QuestionController.getAll(QuestionController.java:64) at com.ptoject.demo.controller.QuestionControllerTest.getAll(QuestionControllerTest.java:12) <31 internal calls> at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)<9 internal calls> at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)<25 internal calls>

