#School Project - SVG Map

21 messages · Page 1 of 1 (latest)

long phoenix
#

Hello, im new to javascript, typescript, and angular and I have a project for school thats due tomorrow. Im trying my best to keep this app to be modular, and make use of components, services, directives, but surely enough I feel like im drowning in info. I know others have kept this project more simple but im trying to persist doing it "the angular way". I was hoping to get some guidance on how I should be structuring my project, and how I can pass data along to components.

The goal is to have a clickable SVG map that makes an HTTP call to retrieve info from the WorldBank website. Right now I have two components, one for the SVG map, another for displaying the data. I have a directive that highlights each country with the tag assigned, I also have an on click function in that directive to give me the id name of the country, and I have a service made to be injected into the directive.

One that last note, how do I import a service to be used in a directive? I feel like the normal injection I would do for components isn't working.

#

*I should add - should I be injecting this service into a directive thats already pulling the id tag, or is there a better way I should be structuring this?

uncut locust
#

You can inject a service into a directive the same way you would inject it everywhere else.
If something doesn't work, post a complete minimal repro as explained in #how-to-get-help . The code does matter, a lot. We can't easily debug things based on just a description of what you're doing.

long phoenix
# uncut locust You can inject a service into a directive the same way you would inject it every...

Okay here is what I got done so far, ignore the highlight code. In the highlight.directive.ts, I have the getId() function which should talk to the service to make an http.get. My current issue is that im not sure how to handle the http.get with the .subscribe. I have seen it written a couple different ways and im not sure whats standard (im using Angular v18)

Let me know if you see anything wrong or weird with my code

country-info.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',

})
export class CountryInfoService {

  private URL = 'https://api.worldbank.org/v2/country/us';
  private countryId = 'us'

  constructor(private http: HttpClient ) {

  }

  getCountryData(id: string): Observable<any>{
    return this.http.get(`${this.URL}/${id}?format=json`)
  }
}

highlight.directive.ts

import {Directive, Input, HostListener, ElementRef, inject, OnInit} from '@angular/core';
import { CountryInfoService } from '../services/country-info.service';

@Directive({
  selector: '[appHighlight]',
  standalone: true,

})
export class HighlightDirective {
  @Input('appHighlight') highlightColor: string = '';
  @Input('id') tagId : string = '';

  private countryService: CountryInfoService = inject(CountryInfoService);
  constructor(private el: ElementRef) {

  }

  @HostListener('mouseenter') onMouseEnter() {
    this.highlight(this.highlightColor);

  }

  @HostListener('mouseleave') onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    this.el.nativeElement.style.fill = color;
  }

  //***ON CLICK BELOW***

  @HostListener('click') onMouseClick() {
    this.getId();
  }

  private getId() {
    this.countryService.getCountryData(this.tagId)

    }
}

uncut locust
#

Well, it depends. What do you want to happen when you click on the element having this directive? You seem to want to get a country... and then what?

long phoenix
#

Oh sorry - I want to pull the data in JSON format, I have to pull 6 properties of the country, then i need to store and use that data in a component to display it on the screen. My guess is that I would use an interface to store the data (in the service maybe?).

My second guess is that I could use signals in that service thats connected to the component that needs to display that data?

uncut locust
#

But a directive is not meant to display things on the screen. That's what components do. So that code shouldn't be in the highlight directive.
You probably want something like this:

@Component({
  selector: 'app-map-with-country-details',
  template: `
    <app-map (countryClicked)="displayedCountryId.set($event)" />
    @if (displayedCountryId(); as countryId) {
      <app-country-details [countryId]="countryId" />
    }
  `
})
class MapWithCountryDetails {
  readonly displayedCountryId = signal<string | undefined>(undefined);
}

@Component({
  selector: 'app-country-details',
  template: `
    @if (countryData(); as countryData) {
      display country data here
    } @else {
      Country data loading in progress...
    }
  `
})
class CountryDetails {
  private readonly countryService = inject(CountryService);

  readonly countryId = input.required<string>();

  readonly country: Signal<CountryData | undefined> = toSignal(
    toObservable(this.countryId).pipe(
      switchMap(countryId => this.countryService.getCountryData(countryId))
    )
  );
}
#

Also, things that you should do:

  • use the latest version of ANgular. Not version 18, which is more that one yer old
  • forget about @Input(). Use signal inputs
  • forget about constructor injection. Use injec().
  • forget @HostListener. Use host metadata.
  • do not use any. Define an interface that describes what you expect to receive from the API, and use that interface.
long phoenix
#

Thank you for the code, I certainly be willing to update my Angular version after this project. I just don't know how far ahead they want the version to be. I want to implement your code but im worried its going to complicated this into a lot of subjects im not sure if I have the mental stack for (sorry I have been up all night last night trying to figure this out). Im all for going about it the "legal" best practices Angular way after this project, but im wondering if I could get away with what I have now.

Is there any chance I could jerry rig my current code to have the directive give the service the id name it pulls on click, which calls the service to pull from the JSON data into an interface with signals, which a component uses that signal interface data to display?

Again though thank you for the code, im just tired

uncut locust
#

You could, but then the service would then need to contain the state (i.e. the data) to be displayed in some other component, and even though that is a valid patter, I don't think it's necessary and logical here.
I'd rather react to the click of a country in a parent component, and give the clicked country ID to the component that actually needs to get and display the data. Or get the data inside the parent component, and give the data to the component which must display it. If you want to not dive too deep into RxJS, you can just subscribe (even though that could lead to subtle race condition bugs, but that you can probably ignore for now). Something like this:

@Component({
  selector: 'app-map-with-country-details',
  template: `
    <app-map (countryClicked)="displayCountry($event)" />
    @if (displayedCountry(); as country) {
      <app-country-details [country]="country" />
    }
  `
})
class MapWithCountryDetails {
  private readonly countryService = inject(CountryService);

  readonly displayedCountry = signal<CountryData | undefined>();

  displayCountry(countryId: string) {
    this.countryService.getCountryData(countryId)
      .subscribe(countryData => this.displayedCountry.set(countryData));
  }
}
long phoenix
#

Okay I’ll try your implementation soon, I’m gonna get back on my computer in 15-20 minutes

#

Ty again

long phoenix
#

Okay im on my computer now, im gonna show a picture of what i have currently (just to make sure you see what im trying to do, but im sure you already know) - and then after ill start reworking my code to what you provided

#

Here I have the SVG map on the left which is one component, on the right is the country data display which is its own component. Both of these components are housed in the "home" component home.component.ts

import { Component } from '@angular/core';
import { WorldmapComponent} from '../components/worldmap/worldmap.component';
import { CountrydatadisplayComponent} from '../components/countrydatadisplay/countrydatadisplay.component';

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [WorldmapComponent, CountrydatadisplayComponent],
  template: `
    <div id="container">
      <app-worldmap id="worldmap"/>
    <app-countrydatadisplay id="datadisplay"/>
  </div>`,
  styleUrl: './home.component.css'
})
export class HomeComponent {

}

Okay now ill try to start reworking your code into mine

long phoenix
long phoenix
#

nvm i see that its an event now

long phoenix
#

School project is completed. Unfortunately the due date was too close and I just didn’t have the time or energy to really be able to understand how or what I was doing to make things work. I hate to say it but I ended up using AI to write the project, I’m normally against using AI to write things for me (honestly it’s the first time I have actually used AI to write any code for me) but is what it is. This class was initially covering JavaScript which was new to me, I didn’t even get the time to really grasp JavaScript before it also threw in Angular into the mix.

That being said, thank you again for trying to help me. I’m going to follow your tips you listed above, update my Angular project, and keep learning Angular for any other project ideas I come up with. Thanks again!