1

I have a component which retrieves a student info from an api upon its initialization. This is the onIniti code on my cmponent-version1

ngOnInit(): void {
    if(!this.student) {
      this.studentsService.getStudentDetail(this.id).subscribe(
        (response: Student) => {
          this.student = response;
        },
        error => console.log(error)
      )
    }
  }

and here is the function inside my student-service-version1

getStudentDetail(id: number): Observable<Student> {
    return this.httpClient.get<Student>(`${this.studentsUrl}${id}/`, this.baseService.httpOptions);
  }

Everything works fine. Now, just for didactic purpose (I'm new to javascript/typescript), I'd like to refactor my service in order to use a single get function which returns the list of students when called without parameter, and instead return a student detail info when called with a specific id. This is the students-service-version2

getStudents(id?: number): Observable<Student[]> {
    if(id)
      return this.httpClient.get<Student[]>(`${this.studentsUrl}${id}/`, this.baseService.httpOptions);
    else
      return this.httpClient.get<Student[]>(this.studentsUrl, this.baseService.httpOptions);
  }

Given that the signature of my function states it returns a students array observable, in my component I need a sort of typecasting from Student[] to Student. This is how I do it: component-version2

ngOnInit(): void {
    if(!this.student) {
      this.studentsService.getStudents(this.id).subscribe(
        (response: Student[]) => {
          this.student = response[0] as Student;
        },
        error => console.log(error)
      )
    }
  }

This doesn't work so after the init, student var remains undefined. I do not understand why, everything seems correct to me (although this refactoring it's not a good idea. Again, I just want to understand the error behind) I'vs also try this.student = response.pop() as Student; Same result, not working.

4
  • How does response looks like? Commented May 5, 2020 at 16:48
  • Where do you check the student var? Because your assignment student = response[0] is in a subscribe, which is asynchronous Commented May 5, 2020 at 16:49
  • using debugger. With version #1 after onInit() the student is ok, with version #2 it results undefined Commented May 5, 2020 at 16:53
  • @brk console.log(response) print this: {id: 18, cf: "ciaciacia", first_name: "Paolo", last_name: "Bianchi", date_of_birth: "2020-05-01", …} as expected, it's the students info Commented May 5, 2020 at 16:58

3 Answers 3

5
ngOnInit(): void {
if(!this.student) {
  this.studentsService.getStudents(this.id).subscribe(
    (response: Student[]) => {
      // Hope, this.student will have type as any, public student: any
      this.student = !this.id ? response[0] as Student : response as Student[];
    },
    error => console.log(error)
  )
}

}

Always, try to return an array to avoid conflicts. In the above code, the ternary operator will do your work. As, if you have an id that means you are asking for particular student information otherwise you are asking for all student records.

Sign up to request clarification or add additional context in comments.

Comments

3

your service should be yelling at you right now because you're lying to the compiler... your return type isn't Observable<Student[]> its Observable<Student[] | Student>... i don't agree with the principal of one function for both single and list gets at all, but you could force it to be a list in the single case...

return this.httpClient.get<Student>(`${this.studentsUrl}${id}/`, this.baseService.httpOptions).pipe(
  map(student => [student])
);

no typecasting will convert something to an array if its not an array. you need to explicitly make it an array if that's waht you want.

6 Comments

I'm sorry that was a typo on the question. I've corrected it. Now it looks like is in my editor and getStudent() return an array of Student in both ways.
if the response actually an array of students? do you have sample JSON of the response itself? inserting a type into get<Type> doesn't actually change anything about the response. it just tells the intellisense what it IS. Typecasting does nothing in typescript other than tell the compiler whats going on. The compiler doesn't can can't know if you're lying to it
your snippet did the trick so I accept it as answer. Still, I do not understand why I need a map while I cannot just extract an element of type Student from an array of Student
because the actual response isn't an array, you can't typecast things into an array if they aren't arrays.
it's not only that, it's that typecasting is JUST for the developer. typescript can't know ahead of time what your server is actually responding with. it can't automagically shape things into what you say you want them to be from what they are.
|
1

Overload the signature of your method as follows:

class StudentService {
  get(id: number) | Observable<Student>;
  get(): Observable<Student[]>;
  get(id: number | undefined): Observable<Student[]> | Observable<Student> {
    if(id !== undefined)
      return this.httpClient.get<Student[]>(`${this.studentsUrl}${id}/`, this.baseService.httpOptions);
    else
      return this.httpClient.get<Student>(this.studentsUrl, this.baseService.httpOptions);
  }
} 

Notice how the method has been renamed to make sense in either case, how the return type is correlated with the presence of the id parameter, and how the check has been modified to accommodate the possibility of 0 as a valid id.

1 Comment

Yes, I don't have even considered the 0 case. Thank you

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.