Accessing variable from other .ts files

Hi there,

I just want to know on how to access the variable from the other .ts files?

Like for example,

Here is my api.service.ts:

This is the variable I want to access

public success : boolean = false;

Function for updating the success variable

 login_clinician(data){
   return new Promise(resolve=> {
     this.http_client.post(this.url+'authenticate/clinicians', JSON.stringify(data), http_options)
     .subscribe(res => {
       resolve(res);
       this.success = true;
     },
     (error) => {
       reject(error);
       console.log(error);
       this.success = false;
     });
   });
 }

Here is the code for another .ts file named login.page.ts. What I want to do is to access the success variable from api.service.ts here:

  login_clinician(){
    this.submit_attempt = true;
    this.login_loader = true;

    let login_data = {
      'student_number' : this.login_clinician_form.value.student_number,
      'password' : this.login_clinician_form.value.password,
    }

    this.api_service.login_clinician(login_data)
    console.log(this.api_service.success);
  }

But when I’m trying to print its value, its undefined.

This is my goal, in login.page.ts I want to access the success variable from api.service.ts. Anyone know how to do it? Thanks!

Likely reason is that the login has not completed when you print the result. Put a console log in the http-subscribe to see when the http has resolved.

Beyond the point that it is a quite pointless to create a promise for a http to complete. But that maybe be the next level for you.
And that you ideally should not want to access variables directly from services. Better use a getter or function. Again, next level.

1 Like

Yah you’re right but when I try to access that variable, the console state that this variable is undefined. How i can implement getter function with this case? :frowning:

A getter seems to me not solving your issue anytime soon. But if you insist on knowing, search it on Google.

As to the problem of not adhering to the async nature of http calls: change the line in login.ts to show

this.api_service.login_clinician(login_data)
.then(stuff=>{
      console.log(this.api_service.success,stuff);
})

instead of

this.api_service.login_clinician(login_data)
console.log(this.api_service.success);

and put the resolve and reject after the this.success=true (or false)

login_clinician(data){
   return new Promise(resolve=> {
     this.http_client.post(this.url+'authenticate/clinicians', JSON.stringify(data), http_options)
     .subscribe(res => {
       this.success = true;
       resolve(res);
     },
     (error) => {
       this.success = false;
       reject(error);
       console.log(error);
     });
   });
 }

If you are serious about this app, please do the Tour of Heroes tutorial before continuing with anything else.

https://angular.io/tutorial

Will save you quite some hassle understanding Angular, which is the thing you seem not fully to grasp looking at the patterns you use.

For instance, one should not want to subscribe in a service, and never convert observables to promises in the way you do - if at all. Subscribes should be handled ideally in the UI endpoint (and then also ideally through async pipes).

So, if you insist on the latter, the cleaner way (even though it still is a bit of an anti-pattern) would be:

    login_clinician(data){
this.success=false;
   return  this.http_client.post(this.url+'authenticate/clinicians', JSON.stringify(data), http_options)
      .pipe(
       tap(res=>{ 
                    console.log('RES',res);
                    this.success=true})
       )
      .toPromise();
 }

To check: what happens if the http fails, not sure if the tap gets activated. I believe so, then you need to do some ugly if/then/else - I would ditch this.success anyway as test on success of the api call in the service. That can and should be handled at the receiving end as it has UI effects.

The caller should be then:

this.api_service.login_clinician(login_data)
.then(stuff=>{
      console.log(this.api_service.success,stuff);
})
.catch(error=>{ console.log('Error happened', error)});
2 Likes

As per usual, @Tommertom has covered the questions actually asked, so I’m going to go off on a tangent that few people probably care about:

Computers are not the only ones reading your code

Pascal bothers to have two distinct keywords for defining functions: Procedure and Function, which is not the worst idea in the world. It’s important for readers to be able to look at a section of code and understand as quickly and viscerally as possible what it is intended to do, because a major class of bugs consists of when code doesn’t do what it’s intended to. Naming things is an important part of that.

In the Go language, naming enforces accessibility. Things that start with lowercase are private: all publicly accessible members of things must Start With Uppercase. JavaScript has no such feature, so it’s up to programmers to enforce conventions. Here’s Google’s set, which codifies what I would consider widely-accepted canon on the topic. api_service should be apiService, and login_clinician should be loginClinician.

Since we’re not in Pascal here, the only way to indicate the difference between a Procedure and a Function is by rigorously declaring return types for everything. You need to decide whether loginClinician is a Procedure or a Function. The way you’ve written this method in the page suggests that you want the service method to be a Function: you care about its result. I don’t know if there is a more logical thing for the service’s loginClinician to return aside from success/failure. For example, maybe it could want to return the clinician id that it received from the API. To just do success/failure, I would simply use RxJS’s native error handling here, and return an Observable<any> (any here being an indication that callers can’t rely on anything the Observable actually emits, just whether it does so or not):

interface Credentials {
  studentNumber: string;
  password: string;
}

loginClinician(creds: Credentials): Observable<any> {
  return this.http.post(this.url + 'authenticate/clinicians', {
    student_number: creds.studentNumber,
    password: creds.password });
}

It’s very rare that one would want different error handling for different services that are all interacting with the network, so I think that is a task better centralized in an interceptor (along with what I presume is intended to be done with loginLoader and friends).

At this point, it’s rather likely that your initial problem has been designed away: IOW, you no longer want to access success from the page, which can simply do:

this.clinicianService.loginClinician(this.form.value).subscribe(() => {
  // if we're in here, the login succeeded
});

However, there’s an outside chance that you want to know something about the currently logged-in clinician (such as their name to display a personalized greeting), or just whether somebody is logged in at all (to maybe redirect to a login page if a session has expired), without being in the context of actually logging in somebody right now. That changes loginClinician from a Function to a Procedure, and decouples the information about the currently logged-in user out of it.

There are, broadly speaking, two major situations here that I’ll call imperative and reactive. Displaying the user’s name is an imperative need: we want something with a signature like getCurrentUserName(): string. Knowing when a login or logout has happened for routing reasons, however, is reactive. We need to be notified whenever there is a change.

Fortunately, RxJS provides the BehaviorSubject, which is capable of servicing both imperative and reactive needs:

interface Clinician {
  studentNumber: string;
  name: string;
}

private activeClinician$ = new BehaviorSubject<Clinician | null>(null);

watchActiveClinician(): Observable<Clinician | null> {
  return this.activeClinician$;
}

peekActiveClinician(): Clinician | null {
  return this.activeClinician$.value;
}

loginClinician(creds: Credentials): void {
  this.activeClinician$.next(null);
  this.http.post<Clinician>(this.url + 'authenticate/clinicians', {
    student_number: creds.studentNumber,
    password: creds.password })
  .subscribe((clinician) => this.activeClinician$.next(clinician));
}

This effectively does let you access the success you were looking for initially, but whenever you want and in two ways.

1 Like

Thank you for considering to answer my question :slight_smile: . Yes, I need to read more about its documentation because I’m actually new here in Ionic 4 - Angular. Its kinda quite hard since my main programming language is python :slight_smile: . Also, I’m not very familiar with the structure of angular its look like something different from usual :slight_smile: . By the way, thanks will try your solution later :slight_smile:

1 Like

Oh I see, thank you for the correction. This is my first time to encounter the procedure context. Basically, login_clinician is the procedure to determine whether the API call succeeded or not. I treat api_service as a function that will return the result ( status code (200, 404) and user credentials ) coming from the API. Like from what you had said, I really need to store the id of the user and save it to somewhere in cached so when the user try to open the application again, his/her will automatically redirected to dashboard page and read his/her details without making any API call.

Technically, my main programming language is Python and that’s the reason why my procedure and function have the same pattern login_clincian & api_service. In Python, the best practice of creating a function are the function’s name should be all small letter and replace the white space with underscore.

By the way, thank you for giving efforts to answer my question. I appreciate it and will try this solution later :slight_smile: . I will give a feedback after I’ve implemented your answer. Thanks see you around :slight_smile: . More power to ionic community.

I try to look for natural places to build walls between parts of programs, because that facilitates several tasks that seem to always come up:

  • understanding how a program is put together
  • figuring out where bugs live
  • testing stuff
  • distributing writing/maintenance responsibility amongst team members
  • reimplementing and improving parts of the program

One of the guidelines I use for figuring out where natural dividing lines are is “can I think of a potential way this could be implemented differently without anybody outside it having to know or care?”. In Ionic applications, one place that seems to consistently be a sweet spot is the line between producers of and consumers of business-layer objects.

By “business-layer object”, I’m talking about things that define a concept in an app that is easily explainable to somebody who maybe uses or markets the app, but has absolutely zero interest in or knowledge about coding. So a Clinician would be a business-layer object, but an HttpResponse would not.

Components and pages tend to be consumers of business-layer objects, and while it is initially tempting for technical reasons to saddle them with technical details about how those business-layer objects come about, every time I’ve done that, I’ve ended up going back and redesigning things so that they don’t. Instead, these pages and components inject services, and those services have a bunch of methods that expose single business objects, arrays of them, observable sequences of them, promises of them, &c.

Those services, however, never expose details of how those objects get wrangled around. This means that we have a situation where we could do things completely differently without the page knowing or caring. Say we want to implement an offline mode later, or a mock service for testing, or have a local cache backed by a network, or a network failover, or a push notification, or a peer-to-peer discovery network, or some other transport method that hasn’t been invented yet. The second that we expose stuff that is dependent on the particular “get from HTTP every time” implementation that we may be starting with, we’re begging page and component authors to lock us into that implementation, making extension a lot harder and making components worry about stuff they shouldn’t ever be seeing in the first place.

If you’re still reading after all that, I’m trying to convince you that api_service should not be leaking any status codes, and should not be named api_service (leaving aside for the moment the case conventions in naming). The fact that it’s getting things from an API is something we actively want to suppress. What we want to promote is that it is capable of producing Clinicians, so I want to call it clinicianService.

ClinicianService can do that seamlessly. api_service is going to have to fake HTTP status codes and other such needless bletchery.

I get that, and if we were talking about Python here, I would totally concur with your casing choices. But in an Ionic app, we as app authors are going to be building on top of a zillion other open-source libraries (including Ionic and Angular themselves), and having our code look idiomatic against that backdrop drastically improves readability. Even posting here in these forums, you are likely to get higher-quality and faster answers when using idiomatic naming conventions, simply because the people reading your post will be able to understand it faster and focus in on the crucial parts instead of getting distracted by unusual style.

Anyway, hope you make progress smoothly.

1 Like

hey @Tommertom I had tried your solution and it works. One more question, how I can access each value in res variable?

hey @Tommertom i found a way on how to read the specific value on response data.

Here’s a piece of code for printing the message value.

console.log(stuff.message);

This is the problem, my IDE ( visual studio code ) state that Property 'message' does not exist on type 'Object'.. But when I check the browser’s console, it printed the value behind the key message. Is it okay to disregard the problem since it is still working and no errors being committed?

Here’s the screenshot for reference ( vscode and browser’s console ):

asd

asd1

of course it works :slight_smile:

The error is a typescript error meaning you ideally want to typecast res to avoid future errors and allow for intellisense during the development.

the dirty way out is to replace .then(res=>{ with .then((res:any)=>{

The more elaborate answer and better quality is a bit beyond my time appetite. :slight_smile:

Tom

1 Like

I vote no, and disagree with the assertion that no errors have been committed. That should cause ionic build to fail with a TS2339 error. The “more elaborate” answer @Tommertom hinted at isn’t really all that more elaborate. See this section of the Tour of Heroes part 6, define an interface, and use it to parameterize the post call:

interface ClinicianResponse {
  message: string;
  // other stuff
}

...

this.http.post<ClinicianResponse>(...).subscribe(res => {
  // res is now a ClinicianResponse
  shouldNotError(res.message)
});
1 Like

Hey @rapropos, sorry but I’ve correction with my question. The data that I’m referring to is the data coming from API and not the data that I’m posting to API. This is the process, authenticate user by sending his/her login credentials to API ( post ), if it is a valid, the API will return the whole details of the user like first name, middle name, last name, etc.

By executing these lines:

    this.authService.loginAsClinician(login_data).then(res => {
      this.login_loader = false;
      this.success = true;
      console.log(res);
    })

Here’s the sample output from httpresponse:
asd1

What I want to do is to access the values for first_name, id, last_name, middle_name, student_number, message, etc. How I can do that? :frowning: Thanks anyway.

That doesn’t matter. You need to declare proper types for things. Internally, loginAsClinician is calling HttpClient.post, yes? If it looks like this:

loginAsClinician(creds: Credentials): Observable<ClinicianResponse> {
  return this.http.post<ClinicianResponse>(...);
}

…then you can call it like so:

this.authService.loginAsClinician(creds).subscribe(rsp => {
  // typescript is happy now, because it knows rsp is a ClinicianResponse
  // and therefore what fields it has
  rsp.message 
});

I figured you had taken @Tommertom’s advice as far as converting the Observable to a Promise using toPromise(), in which case it would return a Promise<ClinicianResponse> automatically, as long as you write the post call with a type: post<ClinicianResponse>(), not just naked post(). If you’re still using the explicit Promise instantiation idiom from your OP, I would suggest reading this SO question, and especially this link referenced in it, and hopefully you will no longer be tempted to do that.