My code compiles but when I call the function I get a CORS error. Reading this answer, it seems that Angular is sending a fetch request to Firebase Cloud Functions, but Firebase Cloud Functions is rejecting the request, and that there's some way to tell Firebase Cloud Functions to accept requests from Angular (http://localhost:4200/)?
I see storage.rules
but I don't see a functions.rules
file. In my Firebase Console I see rules for Firestore and Storage but not for Functions.
I added hosting
to firebase.json
:
"hosting": {
"rewrites": [
{
"source": "/callMe",
"function": "callMe"
},
]
}
I tried httpsCallableFromURL
instead of httpsCallable
:
const addMessage = httpsCallableFromURL(this.functions, 'http://localhost:5001/triggerable-functions-project/us-central1/addMessage');
Here's the documentation for calling Firebase Cloud Functions from an app.
Here's the error message.
Access to fetch at 'https://us-central1-triggerable-functions-project.cloudfunctions.net/addMessage' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
POST https://us-central1-triggerable-functions-project.cloudfunctions.net/addMessage net::ERR_FAILED
...hundreds of frames...
ERROR Error: Uncaught (in promise): FirebaseError: internal
FirebaseError: internal
at resolvePromise (zone.js:1214:31)
at resolvePromise (zone.js:1168:17)
at zone.js:1281:17
at _ZoneDelegate.invokeTask (zone.js:409:31)
at core.mjs:25298:55
at AsyncStackTaggingZoneSpec.onInvokeTask (core.mjs:25298:36)
at _ZoneDelegate.invokeTask (zone.js:408:60)
at Object.onInvokeTask (core.mjs:25606:33)
at _ZoneDelegate.invokeTask (zone.js:408:60)
at Zone.runTask (zone.js:178:47)
I looked at AngularFire but gave up. The code seems to be for AngularFire 6 and Firebase 8, the documentation was last updated August 21, 2021. AngularFire 7 uses Firebase 9, which is substantially different.
Here's my code:
app.component.ts The error throws on line 30.
import { Component } from '@angular/core';
import { getFunctions, httpsCallable, httpsCallableFromURL } from "firebase/functions";
import { initializeApp } from 'firebase/app';
import { getFirestore, setDoc, doc } from "firebase/firestore";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
firebaseConfig = {
apiKey: "...",
authDomain: "triggerable-functions-project.firebaseapp.com",
projectId: "triggerable-functions-project",
storageBucket: "triggerable-functions-project.appspot.com",
messagingSenderId: "...",
appId: "..."
};
firebaseApp = initializeApp(this.firebaseConfig);
db = getFirestore(this.firebaseApp);
messageText: string | null = null;
functions = getFunctions(this.firebaseApp);
callMe(messageText: string | null) {
console.log("Calling Cloud Function: " messageText);
const addMessage = httpsCallable(this.functions, 'addMessage');
// const addMessage = httpsCallableFromURL(this.functions, 'http://localhost:5001/triggerable-functions-project/us-central1/addMessage');
addMessage({ text: messageText }) // throws error here
.then((result) => {
console.log(result.data)
});
};
}
app.component.html makes a form you can type a message and then click Submit
.
<form (ngSubmit)="callMe(messageText)">
<input type="text" [(ngModel)]="messageText" name="message" placeholder="message" required>
<button type="submit" value="Submit">Submit</button>
</form>
index.js is the Cloud Function:
import { initializeApp } from "firebase/app";
import * as functions from "firebase-functions";
import { getFirestore, connectFirestoreEmulator, setDoc, doc } from "firebase/firestore";
const firebaseConfig = {
apiKey: "...",
authDomain: "triggerable-functions-project.firebaseapp.com",
projectId: "triggerable-functions-project",
storageBucket: "triggerable-functions-project.appspot.com",
messagingSenderId: "...",
appId: "..."
};
const firebaseApp = initializeApp(firebaseConfig);
const db = getFirestore(firebaseApp);
connectFirestoreEmulator(db, 'localhost', 8080);
export const addMessage = functions.https.onCall((data, context) => {
try {
const original = snap.data().original;
const uppercase = original.toUpperCase();
return snap.ref.set({ uppercase }, { merge: true });
} catch (error) {
console.error(error);
}
});
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { environment } from '../environments/environment';
// Angular
import { FormsModule } from '@angular/forms';
// AngularFire
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
import { provideFirestore, getFirestore, connectFirestoreEmulator } from '@angular/fire/firestore';
import { provideFunctions,getFunctions, connectFunctionsEmulator, httpsCallable, httpsCallableData, httpsCallableFromURL } from '@angular/fire/functions';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
provideFirestore(() => {
const firestore = getFirestore();
if (!environment.production) {
connectFirestoreEmulator(firestore, 'localhost', 8080);
}
return firestore;
}),
provideFunctions(() => {
const functions = getFunctions();
if (!environment.production) {
connectFunctionsEmulator(functions, 'localhost', 5001);
}
return functions;
}),
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
CodePudding user response:
That fixed it. I changed httpsCallable
to httpsCallableFromURL
. Then GitHub Copilot wrote the rest of the line. Copilot is amazing!
callMe(messageText: string | null) {
const addMessage = httpsCallableFromURL(this.functions, 'http://localhost:5001/triggerable-functions-project/us-central1/addMessage');
addMessage({ text: messageText })
.then((result) => {
console.log(result.data)
});
};
Note that the URL includes the server location. This documentation says that that optimizes performance.
In the function I changed snap
to data
. I also got rid of the config and initialization stuff, the code runs without it.
index.js
import * as functions from "firebase-functions";
export const addMessage = functions.https.onCall((data, context) => {
try {
const original = data.text;
const uppercase = original.toUpperCase();
return uppercase;
} catch (error) {
console.error(error);
}
});
Adding hosting
to firebase.json
was unnecessary, I took that out.
app.component.ts
has too much boilerplate. Let's reference environment
instead.
app.component.ts
import { Component } from '@angular/core';
import { getFunctions, httpsCallableFromURL } from "firebase/functions";
import { initializeApp } from 'firebase/app';
import { getFirestore } from "firebase/firestore";
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);
db = getFirestore(this.firebaseApp);
messageText: string | null = null;
functions = getFunctions(this.firebaseApp);
callMe(messageText: string | null) {
const addMessage = httpsCallableFromURL(this.functions, 'http://localhost:5001/triggerable-functions-project/us-central1/addMessage');
addMessage({ text: messageText })
.then((result) => {
console.log(result.data)
});
};
}