How to call Firebase Cloud Functions from Angular and AngularFire? - google-cloud-functions

I'm trying to understand the AngularFireFunctions documentation. I made a new Angular project and a new Firestore database, installed AngularFire and Firebase, hooked up the Firebase credentials to environments.ts, and initialized firebase-functions, firebase-admin, and firestore.
I fixed a bug in functions/package.json. The initialization program creates this line:
"main": "lib/index.js",
which should be
"main": "src/index.ts",
My directory structure looks like this:
myproject
+- .firebaserc # Hidden file that helps you quickly switch between
| # projects with `firebase use`
|
+- firebase.json # Describes properties for your project
|
+- functions/ # Directory containing all your functions code
|
+- node_modules/ # directory where your dependencies (declared in # package.json) are installed
|
+- package-lock.json
|
+- src/
|
+- index.js # main source file for your Cloud Functions code
|
+- tsconfig.json # if you chose TypeScript
|
+- package.json # npm package file describing your Cloud Functions code
I spun up a new Angular project and set up app.module.ts exactly as the AngularFireFunctions documentation recommends:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { AngularFireModule } from '#angular/fire/compat';
import { AngularFireFunctionsModule, USE_EMULATOR } from '#angular/fire/compat/functions';
import { environment } from '../environments/environment';
#NgModule({
imports: [
BrowserModule,
AngularFireModule.initializeApp(environment.firebase),
AngularFireFunctionsModule
],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ],
providers: [
{ provide: USE_EMULATOR, useValue: ['localhost', 5001] }
]
})
export class AppModule {}
I made a button in my HTML view to call the Firebase Cloud Function:
<div>
<button mat-raised-button color="basic" (click)='callMe()'>Call me!</button>
</div>
I imported firebase-functions and firebase-admin into index.ts as the documentation recommends. Then I uncommented the default function that comes with index.ts and added a console.log.
// The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
export const helloWorld = functions.https.onRequest((request, response) => {
console.log("Hello world!")
functions.logger.info("Hello logs!", {structuredData: true});
response.send("Hello from Firebase!");
});
Finally we get to app.component.ts. I don't understand the provided code in the documentation and it throws errors so I wrote my own controller:
import { Component } from '#angular/core';
import { AngularFireFunctions } from '#angular/fire/compat/functions';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
constructor(private fns: AngularFireFunctions) {}
callMe() {
console.log("Calling...");
this.fns.httpsCallable('helloWorld');
}
}
When I run firebase emulators:start I see an error message:
functions: Failed to load function definition from source: FirebaseError: Failed to load function definition from source: Failed to generate manifest from function source: SyntaxError: Unexpected token 'export'
It's objecting to these lines in app.module.ts and app.component.ts:
export class AppModule {}
export class AppComponent {}
Those aren't errors and the emulator recovers and starts up.
✔ All emulators ready! It is now safe to connect your app. │
│ i View Emulator UI at http://localhost:4000
I click the button in my HTML view, see Calling... in the console log, and nothing happens in the emulator log. I was expecting to see Hello world in the emulator logs. Why doesn't my Angular app call the Firebase Cloud Function?

This does not define a callable function: functions.https.onRequest. It instead defines a regular HTTP function, which you can invoke with something like fetch.
Callable Cloud Functions are defined with functions.https.onCall. To implement a callable function, have a look at the documentation on writing a callable Cloud Function.
In addition this code does not invoke the function yet, but instead merely looks it up:
this.fns.httpsCallable('helloWorld');
To invoke it, you'll need to invoke it with some extra parenthesis, or store it in a variable and then invoke it as shown in the docs.
const callable = fns.httpsCallable('my-fn-name');
this.data$ = callable({ name: 'some-data' });

I've written a tutorial that answers this question. Here's the app.module.ts:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { environment } from '../environments/environment';
// AngularFire 7
// import { initializeApp, provideFirebaseApp } from '#angular/fire/app';
// import { provideFirestore, getFirestore } from '#angular/fire/firestore';
// import { provideFunctions, getFunctions, connectFunctionsEmulator } from '#angular/fire/functions'; // https://firebase.google.com/docs/emulator-suite/connect_functions#instrument_your_app_to_talk_to_the_emulators
// AngularFire 6
import { AngularFireModule } from '#angular/fire/compat';
import { AngularFireFunctionsModule } from '#angular/fire/compat/functions';
import { USE_EMULATOR } from '#angular/fire/compat/functions'; // comment out to run in the cloud
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// AngularFire 7
// provideFirebaseApp(() => initializeApp(environment.firebase)),
// provideFirestore(() => getFirestore()),
// provideFunctions(() => getFunctions()),
// AngularFire 6
AngularFireModule.initializeApp(environment.firebase),
AngularFireFunctionsModule
],
providers: [
{ provide: USE_EMULATOR, useValue: ['localhost', 5001] } // comment out to run in the cloud
],
bootstrap: [AppComponent]
})
export class AppModule { }
This uses AngularFire 6. I haven't been able to get AngularFire 7 to work with callable functions.
This runs the functions in the emulator. To run your functions in the cloud comment out two lines.
Here's app.component.ts:
import { Component } from '#angular/core';
// AngularFire 7
// import { getApp } from '#angular/fire/app';
// import { provideFunctions, getFunctions, connectFunctionsEmulator, httpsCallable } from '#angular/fire/functions'; // https://firebase.google.com/docs/emulator-suite/connect_functions#instrument_your_app_to_talk_to_the_emulators
// import { Firestore, doc, getDoc, getDocs, collection, updateDoc } from '#angular/fire/firestore';
// AngularFire 6
import { AngularFireFunctions } from '#angular/fire/compat/functions';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
data$: any;
constructor(private functions: AngularFireFunctions) {
const callable = this.functions.httpsCallable('executeOnPageLoad');
this.data$ = callable({ name: 'Charles Babbage' });
}
callMe() {
console.log("Calling...");
const callable = this.functions.httpsCallable('callMe');
this.data$ = callable({ name: 'Ada Lovelace' });
};
}
Again this AngularFire 6. The variable data$ handles the data returned from the cloud function.
httpsCallable takes one parameter, the name of the function.
callable executes the function and takes one parameter, an object holding the data to send to the function.
The HTML view:
<div>
<button mat-raised-button color="basic" (click)='callMe()'>Call me!</button>
</div>
{{ data$ | async }}
The view show a button for the user the click and the data returned from the function.
And the index.js cloud functions:
// The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// executes on page load
exports.executeOnPageLoad = functions.https.onCall((data, context) => {
console.log("The page is loaded!")
console.log(data);
console.log(data.name);
// console.log(context);
return 22
});
// executes on user input
exports.callMe = functions.https.onCall((data, context) => {
console.log("Thanks for calling!")
console.log(data);
console.log(data.name);
// console.log(context);
return 57
});
Each functions uses https.onCall((data, context) => {} to make it a callable function, i.e., callable from Angular. data is the data sent from Angular. context is metadata about the execution of the function. Each function returns data, which is displayed in the HTML view.
To run the functions use the emulator:
firebase emulators:start --only functions

Related

Problem with reading from a json file when refreshing th page Angular

I have an angular application and the client wants the path of the Backend in a json file, so he can change it easily whithout needing of another deployment.
Well i did it, but when i refresh the page or close the app and reopen it, the app don't detect the path of the backend, it is like a problem of retard or synchronisation.
This is the error in the console :
http://***/undefinedapi/Leave/GetlistLeave
This is how i did it :
The json file :
{
"ApiRoot": "http://***/"
}
How i read from the constant from the json file :
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs';
import { apiRoot } from '../model/model.apiRoot';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class apiRootService {
static apiRoot: string;
constructor(private http: Http) { }
public initialiseApiRoot()
{
this.http.get('./assets/apiRoot/apiRoot.json').pipe(map((response: Response) =>
<apiRoot>response.json())).subscribe(data => {
apiRootService.apiRoot = data['ApiRoot'];
})
}
}
and then i call this function in the constructor of app.component.ts like this :
this.apiRootService.initialiseApiRoot();
and change the call of the api in every servic elike this :
return this.http.get(apiRootService.apiRoot + .....
Any hlp and thanks
Well, let's suppose you're not facing a cache problem. If it isn't a cache problem, maybe it's a matter of timing.
You can try to set your apiRoot while your app is initializing (before app.component.ts is loaded). You can do that by providing an APP_INITIALIZER as described in Angular docs. If you use a factory that returns a function providing a promise, you'll delay your app initialization until your json file is loaded so you can initialize apiRoot. A factory is a useful approach because it will allow you to inject HttpClient service during initialization in the provider (you'll need it to get your json file).
You can do something like (in your app.module.ts):
...
import {APP_INITIALIZER} from '#angular/core';
...
// Angular will inject the HttpClient because you'll
// tell it that this is a dependency of this factory
// in the providers array
export function getApiRoot(http: HttpClient) {
return () => {
return this.http.get('./assets/apiRoot/apiRoot.json').pipe(
map((response: Response) => <apiRoot>response.json()),
tap((data: any) => apiRootService.apiRoot = data['ApiRoot'])
).toPromise();
};
}
...
#NgModule({
imports: [
...
HttpClientModule,
...
],
providers: [
...
{
provide: APP_INTIALIZER,
useFactory: getApiRoot,
multi: true,
deps: [HttpClient]
}
...
]
})
export class AppModule {}
because you are going with wrong approach. you are seeting url after application is initialized. Refer :- https://medium.com/voobans-tech-stories/multiple-environments-with-angular-and-docker-2512e342ab5a. this will give general idea how to achieve build once and deploy anywhere

How do you return data to your Angular app using proxy.conf.json?

I am using proxy.conf.json as I develop my Angular application.
However I would like to, for a few endpoints, simply return a JSON object when called. Currently my proxy.conf file redirects to a locally running backend which returns these JSONs. However I'd rather not run the backend server and simply return the JSON from proxy.conf.json.
Is this possible somehow?
It is possible by using proxy.conf.js instead of proxy.conf.json. Then you can specify a bypass function where you can return a response directly. This is mentioned in the angular-cli documentation for the proxy but it does not give many details. Here is a sample proxy.conf.js file to do it.
const PROXY_CONFIG = {
'/api': {
'target': 'http://localhost:5000',
'bypass': function (req, res, proxyOptions) {
switch (req.url) {
case '/api/json1':
const objectToReturn1 = {
value1: 1,
value2: 'value2',
value3: 'value3'
};
res.end(JSON.stringify(objectToReturn1));
return true;
case '/api/json2':
const objectToReturn2 = {
value1: 2,
value2: 'value3',
value3: 'value4'
};
res.end(JSON.stringify(objectToReturn2));
return true;
}
}
}
}
module.exports = PROXY_CONFIG;
You need to recheck the url in the bypass function because it is called for all /api requests and then you just directly return a response for the ones you want the others will still be redirected to the target address. You return true to tell the proxy the request finished.
Make sure to then specify the correct file when running ng serve --proxy-config proxy.conf.js.
For returning JSON from proxy.config.conf, there doesn't seem to be an easy way to do it.
One way would be to have a baseApiUrl in the environment.ts file as well urlSuffix set to .json. Then all of the API calls would have to be something like this: enviroment.baseApiUrl + uri + environment.urlSuffix. Then in environment.prod.ts, the urlSuffix would be an empty string. This is a hacky solution but would work.
Alternative using HTTP_INTERCEPTORS
A cleaner solution that leverages the framework, would be to use an HttpInterceptor with the HttpClient along with setting the baseApiUrl in the environment.ts file. This allows for different API endpoints per environment.
environment.ts
export const environment = {
apiBaseUrl: 'http://localhost:4200/assets/api',
production: false
}
development.interceptor.ts
import {Injectable} from '#angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '#angular/common/http';
import {Observable} from 'rxjs/Observable';
import {environment} from '../environments/environment';
#Injectable()
export class DevelopmentInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let clonedRequest = null;
if (!environment.production) {
clonedRequest = request.clone({
url: `${request.url}.json`
});
return next.handle(clonedRequest);
}
return next.handle(clonedRequest);
}
}
This class will intercept any http request made by the HttpClient. Using the properties in the environment.ts file, you can check if the current build is a production build. If it is, clone the request and append .json to it. Anything that is in the assets folder is accessible from the browser. Below is a sample file that maps to the url http:localhost:4200/assets/api/test.json.
/src/assets/api/test.json
{
"name": "test",
"description": "Test Data"
}
Place this file in the assets directory and have the directory structure follow the endpoints of the actual API.
test.service.ts
import {Injectable} from '#angular/core';
import {HttpClient} from '#angular/common/http';
import {Observable} from 'rxjs/Observable';
import {environment} from '../environments/environment';
#Injectable()
export class TestService {
private baseUrl = `${environment.apiBaseUrl}/test`;
constructor(private http: HttpClient) {
}
getTestData(): Observable<any> {
return this.http.get(this.baseUrl);
}
}
Import the environment.ts file here and set the base url to the apiBaseurl property. As long as you import the environment.ts file and not the environment.prod.ts file, this will work in all environments as the appropriate environment file will be read from when the app is built. So this url only has to be change in one place per environment.
Angular CLI Build Targets & Environment Files
app.component.ts
import {Component, OnInit} from '#angular/core';
import {TestService} from './test.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'app';
constructor(private testService: TestService) {
}
ngOnInit() {
this.testService.getTestData().subscribe(
(testData) => console.log(testData)
);
}
}
Here the TestService is injected into the AppComponent and the getTestData() method is called to fetch data from the API. The DevelopmentInterceptor checks the environment and appends .json to the request. Then the data is logged to the console.
app.module.ts
import {BrowserModule} from '#angular/platform-browser';
import {NgModule} from '#angular/core';
import {HTTP_INTERCEPTORS, HttpClientModule} from '#angular/common/http';
import {AppComponent} from './app.component';
import {DevelopmentInterceptor} from './development.interceptor';
import {TestService} from './test.service';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
TestService,
{
provide: HTTP_INTERCEPTORS,
useClass: DevelopmentInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
Register the TestService and the DevelopmentInterceptor as providers.
Using this setup, proxy.config.json is not necessary.
For more information on HttpInterceptors, there is the Angular Documentation Intercepting Http Requests & Responses.
There is also a tutorial by Jason Watmore that does some more advanced things with this approach. Angular 5 - Mock Backend Example for Backendless Development

String Resources in Angular

I'm developing an Angular app and I'm looking for something similar to Android Resource available in Android development.
Here is the way to get a string in Android:
String mystring = getResources().getString(R.string.mystring);
I would like to have the same in Angular.
For example if I have few HTML templates in which there are the same message about the wrong email provided...
<div class="alert alert-danger">
<strong>Error!</strong>Invalid e-mail
</div>
I would like to have the following:
<div class="alert alert-danger">
<strong>Error!</strong>{{myStrings.INVALID_EMAIL}}
</div>
...or something like this...
<div class="alert alert-danger">
<strong>Error!</strong>{{'INVALID_EMAIL' | stringGenerator}}
</div>
Do you know a way or addon I can install to reach that?
Having configuration, translations and resources separated from application's logic is very useful. Configuration would also be very helpful in other context like, for example, getting api_url useful for any rest call.
You can set up such thing using #angular/cli. Having the following application structure:
|- app
|- assets
|- i18n
- en.json
- it.json
|- json-config
- development.json
- env.json
- production.json
|- resources
- en.json
- it.json
|- environment
- environment.prod.ts
- environment.ts
|- config
- app.config.ts
Where:
app: contain all application logics
assets/i18n/*.json: contains a textual resources that can be used in any of your components. There's one of them for each language we want to cover.
E.G. en.json:
{
"TEST": {
"WELCOME" : "Welcome"
}
E.G it.json:
{
"TEST": {
"WELCOME" : "Benvenuto"
}
assets/json-config: contains configuration files to use in development mode and production mode. Also contains env.json which is a json that says which is the current development mode:
E.G. env.json:
{
"env" : "development"
}
E.G. development.json:
{
"API_URL" : "someurl",
"MYTOKEN" : "sometoken",
"DEBUGGING" : true
}
assets/resources: contains jsons files of resources per each language we want to cover. For instance, it may contain jsons initialization's for application models. It's useful if, for example, you want to fill an array of a model to be passed to an *ngFor personalized based on enviroment and/or language. Such initialization should be done inside each component which want to access a precise resource via AppConfig.getResourceByKey that will be shown later.
app.config.ts: Configuration Service that loads resources based on development mode. I will show a snippet below.
Basic Configuration:
In order to load basic configuration files as the application starts we need to do a few things.
app.module.ts:
import { NgModule, APP_INITIALIZER } from '#angular/core';
/** App Services **/
import { AppConfig } from '../config/app.config';
import { TranslationConfigModule } from './shared/modules/translation.config.module';
// Calling load to get configuration + translation
export function initResources(config: AppConfig, translate: TranslationConfigModule) {
return () => config.load(translate);
}
// Initializing Resources and Translation as soon as possible
#NgModule({
. . .
imports: [
. . .
TranslationConfigModule
],
providers: [
AppConfig, {
provide: APP_INITIALIZER,
useFactory: initResources,
deps: [AppConfig, TranslationConfigModule],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
app.config.ts:
As said above, this service loads configuration files based on development mode and, in this case, browser language. Loading resources based on language can be very useful if you want to customize your application. For example, your italian distribution would have different routes, different behavior or simple different texts.
Every Resources, Configuration and Enviroment entry is available trough AppConfig service's methods such as getEnvByKey, getEntryByKey and getResourceByKey.
import { Inject, Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { get } from 'lodash';
import 'rxjs/add/operator/catch';
import { TranslationConfigModule } from '../app/shared/modules/translation.config.module';
#Injectable()
export class AppConfig {
private _configurations: any = new Object();
private _config_path = './assets/json-config/';
private _resources_path = './assets/resources/';
constructor( private http: Http) { }
// Get an Environment Entry by Key
public getEnvByKey(key: any): any {
return this._configurations.env[key];
}
// Get a Configuration Entryby Key
public getEntryByKey(key: any): any {
return this._configurations.config[key];
}
// Get a Resource Entry by Key
public getResourceByKey(key: any): any {
return get(this._configurations.resource, key);
}
// Should be self-explanatory
public load(translate: TranslationConfigModule){
return new Promise((resolve, reject) => {
// Given env.json
this.loadFile(this._config_path + 'env.json').then((envData: any) => {
this._configurations.env = envData;
// Load production or development configuration file based on before
this.loadFile(this._config_path + envData.env + '.json').then((conf: any) => {
this._configurations.config = conf;
// Load resources files based on browser language
this.loadFile(this._resources_path + translate.getBrowserLang() +'.json').then((resource: any) => {
this._configurations.resource = resource;
return resolve(true);
});
});
});
});
}
private loadFile(path: string){
return new Promise((resolve, reject) => {
this.http.get(path)
.map(res => res.json())
.catch((error: any) => {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
})
.subscribe((res_data) => {
return resolve(res_data);
})
});
}
}
translation.config.module.ts
This module sets up translation built using ngx-translate. Sets up translation depending on the browser language.
import { HttpModule, Http } from '#angular/http';
import { NgModule, ModuleWithProviders } from '#angular/core';
import { TranslateModule, TranslateLoader, TranslateService } from '#ngx-translate/core';
import { TranslateHttpLoader } from '#ngx-translate/http-loader';
import { isNull, isUndefined } from 'lodash';
export function HttpLoaderFactory(http: Http) {
return new TranslateHttpLoader(http, '../../../assets/i18n/', '.json');
}
const translationOptions = {
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [Http]
}
};
#NgModule({
imports: [TranslateModule.forRoot(translationOptions)],
exports: [TranslateModule],
providers: [TranslateService]
})
export class TranslationConfigModule {
private browserLang;
/**
* #param translate {TranslateService}
*/
constructor(private translate: TranslateService) {
// Setting up Translations
translate.addLangs(['en', 'it']);
translate.setDefaultLang('en');
this.browserLang = translate.getBrowserLang();
translate.use(this.browserLang.match(/en|it/) ? this.browserLang : 'en');
}
public getBrowserLang(){
if(isUndefined(this.browserLang) || isNull(this.browserLang)){
this.browserLang = 'en';
}
return this.browserLang;
}
}
Ok, and now? How can I use such configuration?
Any Module/Component imported into app.module.ts or any of them that is imported into another custom module that is importing translation.config.module can now automatically translate any interpolated entry based on browser language. For instance using the following snipped will generate Welcome or Benvenuto based on explained behavior:
{{ 'TEST.WELCOME' | translate }}
What If I want to get a resource to initialize a certain array that will be passed to an *ngFor?
In any component, just do that inside the constructor:
. . .
// Just some model
public navigationLinks: NavigationLinkModel[];
constructor(private _config: AppConfig) {
// PAGES.HOMEPAGE.SIDENAV.NAVIGATION contains such model data
this.navigationLinks =
this._config.getResourceByKey('PAGES.HOMEPAGE.SIDENAV.NAVIGATION');
}
Of course you can also combinate resources and configuration.
AndreaM16's answer is definitely thorough, but you sould also consider using an existing package for this instead of writing your own custom code that you need to maintain. To that end, I would recommend checking out ngx-translate (which is used somewhat under the hood in that answer), transloco and Angular's built-in i18n capabilities.
Most of those approaches are based on the magic strings, where your HTML templates and typescript code need to contain strings that (hopefully) match the keys in a JSON or XML file. This presents a serious code maintenance issue which won't be revealed at compile time, only at runtime. I would also advocate for checking out some guides on creating a type-safe translation system:
https://medium.com/angular-in-depth/angular-typed-translations-29353f0a60bc
https://medium.com/angular-in-depth/dynamic-import-of-locales-in-angular-b994d3c07197
You can also use enum file for string values.
string.ts
export enum TEST_STRING {
TEST_ONE = "this is first test string",
TEST_TWO = "this is second test string",
TEST_THREE = "this is third test string"
}
component.ts
import { TEST_STRING } from '../resources/string';
var testString = TEST_STRING.TEST_ONE;
console.log(testString); // result = this is first test string

Load Script Tag in Angular 2 App When Src Attribute is from Web API Call

Context:
I have an Angular 2+ application that makes calls to a web API containing URLs for a src attribute on a script tag that is created by a loadScript function in the AfterViewInit lifecycle hook.
The web API returns a JsonResult and is yielding the data I expect. I was able to interpolate some of the data in the component's template.
Additionally, before I added the call to the web API, the loadScript function was working with a hard-coded argument.
Reading a thread on github. A "member" stated that scripts are not supposed to be loaded on demand. So what I implemented with the loadScript function is essentially a work around, but how else would load them? I don't want to have a seemingly endless amount of script tags sitting in the index.html file.
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Http } from '#angular/http';
#Component({
selector: 'app-agriculture-roadmap',
templateUrl: './agriculture-roadmap.component.html',
styleUrls: ['./agriculture-roadmap.component.css']
})
export class RoadmapComponent implements OnInit, AfterViewInit {
constructor(private _httpService: Http, private _route: ActivatedRoute)
{
}
apiRoadmaps: { roadmapName: string, pdfRoadmapURL: string, jsRoadmapURL: string };
ngOnInit() {
this._httpService
.get('/api/roadmaps/' + this._route.params)
.subscribe(values => {
this.apiRoadmaps = values.json() as { roadmapName: string, pdfRoadmapURL: string, jsRoadmapURL: string };
});
}
async ngAfterViewInit() {
await this.loadScript(this.apiRoadmaps.jsRoadmapURL);
}
private loadScript(scriptUrl: string) {
return new Promise((resolve, reject) => {
const scriptElement = document.createElement('script')
scriptElement.src = scriptUrl
scriptElement.onload = resolve
document.body.appendChild(scriptElement)
})
}
}
If you are using angular cli .
Then place these scripts in
angular-cli.json file under scripts array
scripts:[
.....
]
Please refer this [link] (https://rahulrsingh09.github.io/AngularConcepts/faq)
It has a question on how to refer third party js or scripts in Angular with or without typings.

Load Config JSON File In Angular 2

I want to load Constant File in Angular 2(which is a Normal TypeScript File) having WebAPI EndPoints.
In Angular1.x. we used to have constants for the same.
How in Angular 2 I can Implement the Same?
I have created the .ts file.My main concern lies in how to load the file beforehand every Other class File loads.
.ts file :
export class testAPI {
getAPI = "myUrl";
}
In service file I am using the same by doing Normal Import:
constructor(private http: Http) {
//console.log(this.test);
console.log(this.testing.getAPI);
//this.test.load();
}
I am getting the Console as Undefined.(Must be because my Service class is loading before API Class).
Thanks in Advance.
UPDATES
Inspired with the solution for this particular problem created ngx-envconfig package and published it on NPM registery. It has the same functionalities as it is provided in this answer and even more.
You can have the JSON file somewhere in assets folder like: assets/config. Depending on whether the environment is dev or not you can use two .json files, one for development and one for production. So you can have development.json and production.json files, where each one will keep the appropriate API endpoints.
Basically you need to go through the following steps:
1. Setting up environment (skip this step if you have it already)
Create two files in src/environments folder:
environment.prod.ts
export const environment = {
production: true
};
environment.ts
export const environment = {
production: false
};
2. Create JSON config files
assets/config/production.json
{
"debugging": false,
"API_ENDPOINTS": {
"USER": "api/v1/user",
...
}
}
assets/config/development.json
{
"debugging": true,
"API_ENDPOINTS": {
"USER": "api/v1/user",
...
}
}
3. Create a service as follows
Note depending on the environment, the ConfigService will load the appropriate file
import { Injectable, APP_INITIALIZER } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs';
import { environment } from 'environments/environment'; //path to your environment files
#Injectable()
export class ConfigService {
private _config: Object
private _env: string;
constructor(private _http: Http) { }
load() {
return new Promise((resolve, reject) => {
this._env = 'development';
if (environment.production)
this._env = 'production';
console.log(this._env)
this._http.get('./assets/config/' + this._env + '.json')
.map(res => res.json())
.subscribe((data) => {
this._config = data;
resolve(true);
},
(error: any) => {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
});
});
}
// Is app in the development mode?
isDevmode() {
return this._env === 'development';
}
// Gets API route based on the provided key
getApi(key: string): string {
return this._config["API_ENDPOINTS"][key];
}
// Gets a value of specified property in the configuration file
get(key: any) {
return this._config[key];
}
}
export function ConfigFactory(config: ConfigService) {
return () => config.load();
}
export function init() {
return {
provide: APP_INITIALIZER,
useFactory: ConfigFactory,
deps: [ConfigService],
multi: true
}
}
const ConfigModule = {
init: init
}
export { ConfigModule };
4. Integrate with app.module.ts
import { NgModule } from '#angular/core';
import { ConfigModule, ConfigService } from './config/config.service';
#NgModule({
imports: [
...
],
providers: [
...
ConfigService,
ConfigModule.init(),
...
]
})
export class AppModule { }
Now you can use ConfigService wherever you want get the necessary API endpoints defined in config .json files.
In Angular 4+ projects generated with the Angular CLI, you will have the environment folder out-of-the-box. Inside of it, you will find the environment.ts files from Karlen's answer. That is a working solution for configuration with one caveat: Your environment variables are captured at build time.
Why does that matter?
When you're setting up a CI/CD pipeline for your Angular app, you will generally have a build tool that builds your project (like Jenkins) and a deployment tool (like Octopus) that will grab that package (the dist folder) and deploy to the selected environment, replacing your environment variables with the correct values in the process. If you use the environment.ts files, your environment variables cannot be replaced this way because the environment.ts files do not get included in the dist folder. There is no file your deployment tool can pick up and edit.
What can we do? we can add a JSON configuration file inside of the assets folder. Those files are included by default in the dist folder we will want to deploy. When we want to use an environment variable, we simply import the settings like import config from '[relative/path/to/your/config/file.json]'.
When we do this, we will get something like the following error:
Cannot find module '../../config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension
This is because the typescript compiler tries to import an exported module and cannot find one. We can fix this by adding the following JSON properties/values in our tsconfig.json file.
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
resolveJsonModule allows the typescript compiler to import, extract types from, and generate .json files.
allowSyntheticDefaultImports allows default imports from modules with no default export.
With this in place, we can run our project and we will find that our error is gone and we can use our config values without any issues.
Now, because this config file is included in the dist folder that gets deployed on the server, we can configure our deployment tool to replace the variable values with the values specific to the environment to which we want to deploy. With this in place we can build our Angular app once and deploy it anywhere.
Another added benefit is that most deployment tools like Octopus ship with native JSON support so you can configure it to replace environment variables in your JSON file quite easily. The alternative is using a regex solution to replace environment variables in a .ts file, which is comparatively more complicated and prone to mistakes.
It is possible to import JSON in TypeScript. You need to add typings:
typings.d.ts:
declare module "*.json" {
const value: any;
export default value;
}
And then import like this:
import config from "../config/config.json";
config.json:
{
"api_url": "http://localhost/dev"
}
I had same issue and in the end i give up from .ts and put it in .js :D like this:
configuration.js in root
var configuration = {
'apiHost': 'http://localhost:8900',
'enableInMemoryWebApi': false,
'authMode': 'standalone',
'wsUrl': 'ws://localhost:8900/ws'
};
module.exports = configuration;
in .ts file for ex. user.service.ts
let configuration = require('../configuration'); //in import section
#Injectable()
export class UserService {
...
getUser(id: number | string): Promise<User> {
console.log(configuration.apiHost) //will get propertye from .js file
return this.http.get(`${configuration.apiHost}/${id}`, this.headers).toPromise().then(this.extractData).catch(this.handleError);
}
}
Hope it helps
You can use Opague token to set constant values as providers
Try:
In your const file:
import { OpaqueToken } from '#angular/core';
export const CONFIG_TOKEN = new OpaqueToken('config');
export const CONFIG = {
apiUrl: 'myUrl'
};
In your AppModule set to make it a singleton provider for the app:
providers:[
//other providers,
{provide: CONFIG_TOKEN, useValue: CONFIG}
]
For injecting in constructor,
constructor( #Inject(CONFIG_TOKEN) private config)