2

I have a service in Angular 7 that calls an API and I want to use some data gathered by a component in the API call. Here is the relevant portion of the component:

import { Component, OnInit } from "@angular/core";
import { IPrompt } from './prompt';
import { PromptService } from './prompt.service';
import { DropdownService } from '../dropdown.service'
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
import { IQuery } from './prompt'
import { NgxSpinnerService } from 'ngx-spinner';

import { Query } from '../dropdown.service';


@Component({
  selector: 'pm-prompts',
  templateUrl: './prompt-list.component.html',
  styleUrls: ['./prompt-list.component.css']
})

export class PromptListComponent implements OnInit 
{

  public get searchArray(): string[] 
  {
  return [ this.searchForm.controls['searchQuery'].value, this.searchForm.controls['region'].value, 
  this.searchForm.controls['status'].value, this.searchForm.controls['language'].value, 
  this.searchForm.controls['inputMode'].value ];
  }

  public get QueryString(): string
  { 
    var query = new Query(this.searchForm.controls['searchQuery'].value, this.searchForm.controls['region'].value, 
    this.searchForm.controls['status'].value, this.searchForm.controls['language'].value, 
    this.searchForm.controls['inputMode'].value, this.searchArray);
    return query.getQuery;
  }
}

I removed all the unnecessary fields from the above sample. Basically, I want to access the get QueryString() method that is in this component from my service called PromptService. I can include the code for the service as well if needed. I've researched this but all of the questions I see has asked how to use data from a service in a component, not the other way around.

I tried importing the component to the service then using:

queryString = PromptListComponent.QueryString()

But I get an error saying that "Property 'QueryString' does not exist on type 'typeof PromptListComponent'". I feel that there is a simple fix for this but I have had no luck finding it or figuring it out.

4
  • how do you initiate the API Call? From which component? Commented Jul 10, 2019 at 16:45
  • The easiest way to go in that direction would be to use an EventEmitter. Trying to access data in a component from a service is a little wonky though. Commented Jul 10, 2019 at 17:26
  • can you please put code where you are importing PromptListComponent. I think you can check Ethan Vu answer. you should create instance of PromptListComponent class then user QueryString(). Commented Jul 10, 2019 at 18:38
  • You can also use 'let' keyword to create instance. Check typescriptlang.org/docs/handbook/modules.html Commented Jul 10, 2019 at 18:46

3 Answers 3

4

You can import the component to the service and define it in your constructor, it generate an instance of that component, then you can access methods and properties from that component's instance:

In your service file:

import { PromptListComponent } from './path-to-your-component';
import { Injectable } from '@angular/core';

@Injectable({providedIn:'root'}) //Or import to providers
export class PromptService {
 //Define it in your service constructor
 constructor( private promptListComponent: PromptListComponent){}

 //You now have generated an instance of that component
 getQueryFromPromtListComponent() {
   console.log(this.promptListComponent.QueryString())
 }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I don't understand how this could ever work.... your component function shows that the component is using formControls that could only possibly be populated by a user, the instance injected here would be a "blank" instance that couldn't possibly be touched by a user....
Can a service have a component as provider? How?
3

The reason everything you see is about accessing data from a service in a component is because you've inverted control. Your logic should be that the component is PUSHING this data to the service, not that the service is PULLING this data from your component.

your theoretical service should have a sort of setQueryString(queryString: string) method that accepts the query string as an argument, and your component, whenever the query string changes, calls that services method. since your already using reactive forms, this is quite simple!

this.searchForm.valueChanges.subscribe(value => this.myService.setQueryString(this.QueryString));

there are MANY alternative ways to do this, but the principle should be the same, data is always being pushed, never pulled.

1 Comment

You're right. It turns out I needed to make the API call from the component anyways so I will keep this in mind for the future. Thanks
2

Assuming that you are not listening from an event from the UI, but executing an action in the service that need to get the component property.

I am leveraging the Visitor pattern for this solution.

There are a couple of things that you appear to be missing, based on provided sample code.

  1. Properties are not accessible through the class type, it only can be done with static members.
  2. To access a property (non static) you need to get the component instance. It needs to be passed to the service.

Ok, now that we have some context to work from, lets see a potential way to achieve what you are looking for:

  1. Add the service (PromptService) as a dependency to the component via the constructor. This will help your service(s) from having direct references to your UI components.
  2. Create an interface that your component will implement which will define a function receiving the property name which value must be returned.
public interface IPropertyReader {
   readProperty<T>(prop: string): T
}
  1. That function will be used to return the specified property value to the calling service.
@Component({
  selector: 'pm-prompts',
  templateUrl: './prompt-list.component.html',
  styleUrls: ['./prompt-list.component.css']
})

export class PromptListComponent implements OnInit, IPropertyReader
{
   constructor(private promptService: PromptService) {
      promptService.registerComponent(this)
   }
   public readProperty<T>(prop: string):T {
      if (!this.hasOwnProperty(prop)) {
         throw Error(`Property "${prop}" does not exists`);
      }
      return this[prop];
   }   
}
  1. Modify your service by adding a function receiving an instance of the component, but the parameter type must be the interface from point 2 above.
@Injectable({ providedIn: 'root' }) 
export class PromptService {
  component: IPropertyReader
  public registerComponent(comp: IPropertyReader) {
     this.component = comp;
     console.log(this.component.readProperty<string>("QueryString"));
  }
}

1 Comment

Really helpful. The potential fix #1 was the route that I went and I got the result I was looking for. Thanks

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.