I have search on the Angular documentation and here on stackoverflow but nothing looks like my issues. I may not have understand something. But my problem is the following :
Angular CLI: 11.0.2 Node: 14.15.1
My Service :
export class ApiService {
constructor(private httpClient: HttpClient) { }
/**
* Get teams by id
* @param id
* @returns
*/
getTeamFromServe(id: number){
return this.httpClient.get('https://api.football-data.org/v2/teams/'+ id, {
headers: {
'X-Auth-Token': 'XXXXXXXXXXXXXXXXX'
},
observe: 'body',
responseType: 'text'
})
}
}
In my componant :
team-details.components.js :
@Component({
selector: 'app-team-detail',
templateUrl: './team-detail.component.html',
styleUrls: ['./team-detail.component.scss']
})
export class TeamDetailComponent implements OnInit {
arrayTeamInfo: Array<any> = [];
constructor(private apiService: ApiService,
private router: Router,
private route: ActivatedRoute) { }
ngOnInit(): void {
const id = this.route.snapshot.params['id'];
this.apiService.getTeamFromServe(id).subscribe(
data => {
let arrayData = [];
arrayData = JSON.parse(data);
this.arrayTeamInfo = arrayData;
console.log(this.arrayTeamInfo);
}, err => {
this.arrayTeamInfo = JSON.parse(err.error).message;
}
)
}
}
result consol.log :
activeCompetitions: []
address: "null Rio de Janeiro, RJ null"
area: {id: 2032, name: "Brazil"}
clubColors: "Red / Black / White"
crestUrl: "https://crests.football-data.org/1783.svg"
email: null
founded: 1919
id: 1783
lastUpdated: "2020-09-10T02:18:46Z"
name: "CR Flamengo"
phone: null
shortName: "Flamengo"
squad: []
tla: "FLA"
venue: "Estadio Jornalista Mário Filho"
website: "https://www.flamengo.com.br/pagina-inicial-basquete"
team-detail.component.html :
<h1>
{{ arrayTeamInfo.name }}
</h1>
This problem occur for each properties like (address, clubColors,email, ...).
And i'm getting this error :
Error: src/app/teams/team-detail/team-detail.component.html:9:38 - error TS2339: Property 'name' does not exist on type 'any[]'.
But if i'm doing this on my html file it's working :
<h1>
{{ arrayTeamInfo['name']}}
</h1>
I've just random this syntax and I don't understand why I have to do this here while in every other componant I have created (with the very same structure) I got no problem...
Have I missing a part or something ?
In the component class, you said:
arrayTeamInfo: Array<any> = []
So arrayTeamInfo
is definitely going to be an array; its name starts with "array", it's got an array type (Array<any>
is the same as any[]
), the default value is an error. Perfectly clear. You reinforce this when receiving data:
let arrayData = [];
arrayData = JSON.parse(data);
this.arrayTeamInfo = arrayData;
The empty array value is unused, but does set the type of arrayData
("array" again!) to any[]
. This is 100% an array, certainly not going to be not an array. Right? But then in the template, you do:
<h1>
{{ arrayTeamInfo.name }}
</h1>
Arrays don't have names, so when the compiler type-checks this it's naturally a little bit confused. Hence the (entirely accurate) error:
Property 'name' does not exist on type 'any[]'.
In your question you show the team information very much not being an array and say that arrayTeamInfo['name']
does work, so the question is: why did you spend so much time convincing the compiler your value was going to be an array if it's not?
So how do you fix it? Firstly, you should express the actual shape of the team information somewhere in your codebase:
interface Team {
name: string;
area: {id: number; name: string};
/* ...other props */
}
Then you can actually use this when you're fetching the data from the server, so the rest of your code knows what it should be expecting to receive - specify the method's return type, and use the generic type argument to HttpClient#get
(see https://angular.io/guide/http#requesting-a-typed-response):
export class ApiService {
constructor(private httpClient: HttpClient) { }
getTeamFromServe(id: number): Observable<Team> {
return this.httpClient.get<Team>( // <- set this
`https://api.football-data.org/v2/teams/${id}`,
{
headers: {'X-Auth-Token': 'XXXXXXXXXXXXXXXXX'},
/* other options restated defaults or were wrong */
}
)
}
}
Finally, in your component, the type of the property should actually reflect what you're planning to assign to it, so the compiler can check that what you've written makes sense:
@Component({
selector: 'app-team-detail',
templateUrl: './team-detail.component.html',
styleUrls: ['./team-detail.component.scss']
})
export class TeamDetailComponent implements OnInit {
teamInfo?: Team; // <- no initial value, so undefined until request completes
constructor(private apiService: ApiService,
private router: Router,
private route: ActivatedRoute) { }
ngOnInit(): void {
const id = this.route.snapshot.params['id'];
this.apiService.getTeamFromServe(id).subscribe(data => {
this.teamInfo = data; // <- data is typed as Team, so the compiler's happy
})
}
}
<h1>
{{ teamInfo?.name }}
<!-- safe navigation handles undefined default -->
</h1>
(Note that the component no longer needs to JSON.parse
the data from the service - the point of having a separate service is to abstract away the details of the transport layer from the presentation layer, so having something like "we're getting data as a JSON string" leak into the component isn't helpful.)
Another option is to lean more into observables, using the | async
pipe rather than needing safe navigation; I wrote about that on my blog a few years ago (using the old Http
syntax, but the ideas are still useful).