简体   繁体   中英

Access component data from a service

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.

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())
 }
}

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.

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"));
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM