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)
Related
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
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
Im trying to reading a JSON config file from my assets in angular project. I have created a Service and calling in App.Module.ts in a initializeApp function.
my jSON files are in src/assets/config/ folder
When it starts it shows me in console a Empty Json file "{}".
This is my service.
import { IConfig } from "../_model/config";
import { Injectable } from '#angular/core';
import { environment } from 'src/environments/environment';
import { HttpClient, HttpResponse } from '#angular/common/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
#Injectable()
export class Config {
static settings: IConfig;
constructor(private http: HttpClient) {
}
load() {
const jsonFile = `assets/config/config.${environment.name}.json`; // ${environment.name} could be 'prod' or 'dev'
return new Promise<void>((resolve, reject) => {
this.http.get(jsonFile).toPromise().then((response: IConfig) => {
Config.settings = <IConfig>response;
resolve();
}).catch((response: any) => {
reject(`Error en archivo de configuracion '${jsonFile}': ${JSON.stringify(response)}`);
});
});
}
and Config interface if you want check:
export interface IConfig {
config: {
production: boolean;
};
path: {
url_root: string;
};
timeZone: {
time: number;
};
}
console error
core.js:15724 ERROR Error en archivo de configuracion 'assets/config/config.dev.json': {}
it always goes for Catch and prints error message with anf EMPTY JSON, like if not exists.
If you need more info just tell me.
I think you have to add the "src/assets/config/" path to the assets: [] array your angular.json file. Then I think you have to recompile the server.
You need to step back twice. ../../
replace
const jsonFile = `assets/config/config.${environment.name}.json`;
with
const jsonFile = `../../assets/config/config.${environment.name}.json`;
Secure Recommention
However, I recommend that you have to place the config inside the environment files instead of the assets folder because the environment is secured by the browsers while assets will be public to anyone.
I have a file for configuring my OpenID Connect authentication
export const authMgr = new Oidc.UserManager({
userStore: new Oidc.WebStorageStateStore(),
authority: **appsetting.oidc**
})
I want to access my state in order to get the value of appsetting.
I did this:
import store from './store'
const appsetting = () => store.getters.appsetting
but my appsetting is always returning undefined
what I my missing?
Store:
app.js
const state = {
appsetting: appsetting,
}
export {
state
}
getters.js
const appsetting = state => state.appsetting
export {
appsetting
}
index.js
export default new Vuex.Store({
actions,
getters,
modules: {
app
},
strict: debug,
plugins: [createLogger]
})
when I print the value of store.getters, it returns this:
{
return __WEBPACK_IMPORTED_MODULE_2__store__["a" /* default */].getters;
}
No the actual store objects
Try to import 'store' with curly brackets
import {store} from '../store/index'
store.getters.appSettings
Another option is to access from the vue property
import Vue from 'vue'
Vue.store.getters.appSettings
As for 2023 accesing store with
import {store} from '../store/index'
store.getters.appSettings
wasnt working for me but after removing curly brackets like so:
import store from '../store/index'
store.getters.appSettings
It started working as intended
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