skip to Main Content

httpsCallableFromURL is working correctly but httpsCallable is returning null. What’s wrong?

The emulator logs show that the Cloud Function executes correctly from both httpsCallableFromURL and httpsCallable. My browser console logs UPPERCASE (if I enter uppercase) with httpsCallableFromURL. But with httpsCallable the browser console logs null.

It seems like null logs almost instantly but UPPERCASE takes a moment to log. My guess is that the return is executing before the function?

And another question. I found the URL in the Firebase Console in the list of Functions, for the deployed function. Is there somewhere in the emulator where I can find the URL for calling the function in the emulator? This URL was written by Github Copilot! This URL also works: http://localhost:5001/my-projectId/us-central1/upperCaseMe

index.ts

import * as functions from "firebase-functions";

export const upperCaseMe = functions.https.onCall((data, context) => {
    const original = data.text;
    const uppercase = original.toUpperCase();
    functions.logger.log('upperCaseMe', original, uppercase);
    return uppercase;
});

app.component.ts

import { Component } from '@angular/core';
import { getFunctions, httpsCallable, httpsCallableFromURL } from "firebase/functions";
import { initializeApp } from 'firebase/app';
import { environment } from '../environments/environment';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  firebaseConfig = environment.firebaseConfig;
  firebaseApp = initializeApp(this.firebaseConfig);

  messageText: any;
  functions = getFunctions(this.firebaseApp);

  callMe(messageText: any) {
    console.log("Calling Cloud Function: " + messageText);
    // const upperCaseMe = httpsCallable(this.functions, 'upperCaseMe');
    const upperCaseMe = httpsCallableFromURL(this.functions, 'http://127.0.0.1:5001/my-projectId/us-central1/upperCaseMe');
    upperCaseMe({ text: messageText })
      .then((result) => {
        console.log(result.data)
      })
      .catch((error) => {
        console.error(error);
      });;
  };
}

I was able to make httpsCallableFromURL return null:

import * as functions from "firebase-functions";

export const upperCaseMe = functions.https.onCall((data, context) => {
    const original = data.text;
    const uppercase = original.toUpperCase();
    setTimeout(() => {
        console.log("Wait ten seconds.");
        functions.logger.log('upperCaseMe', original, uppercase);
        return uppercase;
      }, 10000)
});

This never logs anything in the emulator and always logs null in the browser console. It logs null after a second or two, not ten seconds. Same results with httpsCallableFromURL and httpsCallable.

2

Answers


  1. Chosen as BEST ANSWER

    I initialized Firebase incorrectly. Firebase should be initialized in app.module.ts, not in app.component.ts. Firebase should then be injected into app.component.ts. When I initialized Firebase correctly then both httpsCallable and httpsCallableFromURL returned the UPPERCASE message to the browser console.

    app.module.ts

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { AppComponent } from './app.component';
    
    // Angular
    import { environment } from '../environments/environment';
    import { FormsModule } from '@angular/forms';
    
    // AngularFire
    import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
    import { provideFunctions,getFunctions, connectFunctionsEmulator } from '@angular/fire/functions';
    
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule,
        FormsModule,
        provideFirebaseApp(() => initializeApp(environment.firebase)),
        provideFunctions(() => {
          const functions = getFunctions();
          if (!environment.production) {
            connectFunctionsEmulator(functions, 'localhost', 5001);
          }
          return functions;
        }),
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    I changed the view to pass the message only via ngModel. Passing the message via callMe(messageText) was redundant.

    app.component.html

    <h3>Call cloud function</h3>
    <form (ngSubmit)="callMe()">
        <input type="text" [(ngModel)]="messageText" name="message" placeholder="message" required>
        <button type="submit" value="Submit">Submit</button>
    </form>
    

    I changed the component controller to inject Functions rather than initialize Firebase in the component controller:

    app.component.ts

    import { Component, Inject } from '@angular/core';
    import { Functions, httpsCallable, httpsCallableFromURL } from '@angular/fire/functions';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    
    export class AppComponent {
      constructor(
        @Inject(Functions) private readonly functions: Functions,
      ) {}
    
      messageText: string = '';
    
      callMe() {
        console.log("Calling Cloud Function: " + this.messageText);
        const upperCaseMe = httpsCallable(this.functions, 'upperCaseMe');
        // const upperCaseMe = httpsCallableFromURL(this.functions, 'http://127.0.0.1:5001/languagetwo-cd94d/us-central1/upperCaseMe');
        upperCaseMe({ text: this.messageText })
          .then((result) => {
            console.log(result.data)
          })
          .catch((error) => {
            console.error(error);
          });;
      };
    }
    

    No changes to index.ts.

    As for Doug Stevenson's comment that the last code block in my question made him cringe, I reread the Cloud Functions documentation Sync, async, and promises which explained that return is synchronous and promises are async. I promise never to put a return in a promise again!


  2. httpsCallable uses your deployed code hosted by Cloud Functions by default. The URL is derived from the data you passed during initialization of the Firebase SDK. It does not use the emulator unless you configure the SDK to do so. I suggest reviewing the documentation on the matter.

    To instrument your app to interact with the emulators, you may need to
    do some additional configuration.

    If your prototype and test activities involve callable backend
    functions, configure interaction with the Cloud Functions for Firebase
    emulator like this:

    import { getApp } from "firebase/app";
    import { getFunctions, connectFunctionsEmulator } from "firebase/functions";
    
    const functions = getFunctions(getApp());
    connectFunctionsEmulator(functions, "localhost", 5001);
    

    httpsCallableFromURL is for cases when you cannot use the normal way that the SDK locates the backend, for example, if you are hosting the callable function on some service that is not a normal Cloud Functions backend, or you are trying to invoke a function in a project that’s not part of your app.

    I would never expect Copilot to generate the correct code under any circumstance. It’s better to lean on the documentation to tell you what to do.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search