Ionic3 / Ionic2+ settings.json file for environment variable configuration [duplicate] - javascript

I'm working on an ionic2 project and I need to create a new custom JSON config file. I found some tutorials to create one and access it through http.get but I think it's weird to call it through a get request. I want it in the root folder (where all the config JSONs are) and I open/read the file directly.
I don't know if it's possible, or even recommended ? This is why I'm posting here to have some opinions and solutions :)
Thanks

Personally I don't like the read the config.json file by using the http.get way to handle configuration information, and even though there must be another way to just include and read the json file in your code, since we're using Angular2 and Typescript, why not using classes, interfaces and doing it in a more fancy way?
What I'll show you next may seem more complicated than it should at first (although after reading it you will find it very straightforward and easy to understand), but when I started learning Angular2 I saw an example of how they handled config files in the Dependency Injection guide and I followed that in the apps I've worked on to handle config information (like API endpoints, default values, and so on).
According the docs:
Non-class dependencies
[...]
Applications often define configuration objects with lots of small
facts (like the title of the application or the address of a web API
endpoint) but these configuration objects aren't always instances of a
class.
One solution to choosing a provider token for non-class dependencies
is to define and use an OpaqueToken
So you would need to define a config object with the urls and so on, and then an OpaqueToken to be able to use it when injecting the object with your configuration.
I included all my configuration in the app-config.ts file
// Although the ApplicationConfig interface plays no role in dependency injection,
// it supports typing of the configuration object within the class.
export interface ApplicationConfig {
appName: string;
apiEndpoint: string;
}
// Configuration values for our app
export const MY_CONFIG: ApplicationConfig = {
appName: 'My new App',
apiEndpoint: 'http://www...'
};
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new OpaqueToken('config');
What OpaqueToken is may be confusing at first, but it just a string that will avoid naming conflicts when injecting this object. You can find an amazing post about this here.
Then, you just need to include it in the page you need it like this:
import { NavController } from 'ionic-angular/index';
import { Component, OpaqueToken, Injectable, Inject } from "#angular/core";
// Import the config-related things
import { MY_CONFIG_TOKEN, MY_CONFIG, ApplicationConfig } from 'app-config.ts';
#Component({
templateUrl:"home.html",
providers: [{ provide: MY_CONFIG_TOKEN, useValue: MY_CONFIG }]
})
export class HomePage {
private appName: string;
private endPoint: string;
constructor(#Inject(MY_CONFIG_TOKEN) private config: ApplicationConfig) {
this.appName = config.appName;
this.endPoint = config.apiEndpoint;
}
}
Please notice how to include it in the providers array
providers: [{ provide: MY_CONFIG_TOKEN, useValue: MY_CONFIG }]
And how to tell the injector how it should obtain the instance of the config object
#Inject(MY_CONFIG_TOKEN) config: ApplicationConfig
UPDATE
OpaqueToken has been deprecated since v4.0.0 because it does not support type information, use InjectionToken<?> instead.
So instead of these lines:
import { OpaqueToken } from '#angular/core';
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new OpaqueToken('config');
Now we should use
import { InjectionToken } from '#angular/core';
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new InjectionToken<ApplicationConfig>('config');

After reading and reading different solutions I ended up using this hacky implementation. Hopefully there will be a nice and native solution available soon:
import { NgModule } from '#angular/core';
import { environment as devVariables } from './environment.dev';
import { environment as testVariables } from './environment.test';
import { environment as prodVariables } from './environment.prod';
export function environmentFactory() {
const location = window.location.host;
switch (location) {
case 'www.example.org': {
return prodVariables;
}
case 'test.example.org': {
return testVariables;
}
default: {
return devVariables;
}
}
}
#NgModule({
providers: [
{
provide: 'configuration',
useFactory: environmentFactory
}
]
})
export class EnvironmentsModule {}
and then where ever needed, e.g.:
import { Injectable, Injector, Inject } from '#angular/core';
import { AuthenticationService } from '../authentication';
#Injectable()
export class APIService {
private http: Http;
private apiURL: string;
protected authentication: AuthenticationService;
constructor(
public injector: Injector,
#Inject('configuration') public configuration: any
) {
this.http = injector.get(Http);
this.authentication = injector.get(AuthenticationService);
this.apiURL = configuration.apiURL;
};
...

Related

NestJS: Enforce implementation of a service at *compile-time*

To keep it brief:
I have a service - let's call it CatsService...
import { Injectable } from '#nestjs/common';
#Injectable()
export class CatsService {
GetCat(id: number){
return id + 12345;
}
}
Later on, a module wishes to use this CatsService in order to inject it into a constructor. It also dynamically resolves this CatsService
import { Module } from '#nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import * as ProductionCatsService from "./production"
import * as DevelopmentCatsService from "./development"
const catsServiceProvider = {
provide: CatsService,
useValue: process.env.NODE_ENV === "development" ?
DevelopmentCatsService :
ProductionCatsService
}
#Module({
controllers: [CatsController],
providers: [catsServiceProvider],
exports:[CatsService]
})
export class CatsModule {}
Both of these service implementations are strictly empty. This means that the /cats/:id route returns:
{"statusCode":500,"message":"Internal server error"}
I have had the idea since beginning writing this to make everything implement a ICatsService interface.
After a lot of fiddling about, I have build a sort-of solution:
I have a "service_interface" file, which has an abstract class called ICatsInterface with one abstract method (GetCat)
I have made every one of these implementations implement this service_interface.
I have made the controller take ICatsService instead of CatsService
My problem is that now, the thing compiles, but I get the below error:
TypeError: this.catsService.GetCat is not a function
at CatsController.get (/home/a/learning-nest/src/cats/cats.controller.ts:12:33)
at /home/a/learning-nest/node_modules/#nestjs/core/router/router-execution-context.js:38:29
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at /home/a/learning-nest/node_modules/#nestjs/core/router/router-execution-context.js:46:28
at /home/a/learning-nest/node_modules/#nestjs/core/router/router-proxy.js:9:17
I simply don't know what to do anymore. Even though I have made it compile, I cannot make it actually be forced to implement the interface/abstract class that I want. I have made sure that both DevelopmentCatsService and ProductionCatsService have the implementation of GetCat. They are both marked with #Injectable. I don't truthfully know where to go from here.
Should I abandon my dream of having an compile-time error message for when someone does not implement the methods that I want implemented in my service?
I have solved my own issue. It was very simple.
import { Module } from '#nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import * as ProductionCatsService from "./production"
import * as DevelopmentCatsService from "./development"
const catsServiceProvider = {
provide: CatsService,
useValue: process.env.NODE_ENV === "development" ?
DevelopmentCatsService :
ProductionCatsService
}
#Module({
controllers: [CatsController],
providers: [catsServiceProvider],
exports:[CatsService]
})
export class CatsModule {}
In the above snippet, I am importing "* as ProductionCatsService". This caused my ProductionCatsService to actually be an object wrapping around the real service implementation that I wanted!
Sorry for the bother, all.

Globle module without import not working nestjs

I am trying to implement a global module in nest.js
I have created a service like below
export interface ConfigData {
DB_NAME: string;
}
#Injectable()
export class ConfigManager {
private static _instance?: ConfigManager;
public static configData:ConfigData | null;
private constructor() {
console.log(ConfigManager)
if (ConfigManager._instance)
ConfigManager._instance = this;
else{
ConfigManager._instance = new ConfigManager()
ConfigManager.configData = <ConfigData|null><unknown>ConfigManager.fetchSecretData()
}
}
private static async fetchSecretData():Promise<ConfigData|null>{
// some data from db
}
// static get instance() {
// return ConfigManager._instance ?? (ConfigManager._instance = new ConfigManager());
// //return ConfigManager._instance ?? (ConfigManager._instance = ConfigManager.fetchSecretData()) //new ConfigManager());
// }
}
configuration.module.ts
#Global()
#Module({
providers: [ConfigManager],
exports: [ConfigManager],
})
export class ConfigurationModule {}
and in app.module.ts added ConfigurationModule in imports.
Also adding private constructor on service unable it to add in module.ts file.
I am expecting that I should be able to configData anywhere without importing the ConfigManager. but it's not working...
ConfigManager is not available without import.
You've only marked your module with #Global decorator, but the NestJS needs to somehow initialize that module and make it globally available.
What this means is that you have to add this module to your core application module and NestJS will do the rest for you, so something like this (or however your root module is named):
#Module({
imports: [ConfigurationModule],
})
export class AppModule {}
From the documentation
The #Global() decorator makes the module global-scoped. Global modules
should be registered only once, generally by the root or core module.
Global modules in nest only means you don't have to include that module in the imports: [] of every other module that requires it's providers. The providers in the global module still behaves as as normal providers i.e. you need to inject it where you need it.
So in your case since you have already added #Global() to ConfigManager and imported ConfigurationModule in app.module.ts, you will not need to add ConfigurationModule in imports for any other modules who want to use ConfigManager. You would, however, still need to inject the ConfigManager provider - that's what #Injectable() means :).
To do the injection, you need a constructor(private configManager: ConfigManager) {} in the class that needs to use ConfigManager, and since you need access to the type of the class, you'll need import { ConfigManager } from '../path/to/ConfigManager' as well.
this is 'per-design' of es6/ts: you cant use the class without importing it.
you are mixing the concepts of di (instantiation/composition) with importing (defining which classes are available in the module scope)

Tree shaking for libraries in Angular 8

I'm planning on making a standalone Angular service for Angular 8+, and I've been reading up on how to make it tree tree-shakable.
I believe all we need to do is this:
#Injectable({
providedIn: 'root',
useFactory: () => new Service('dependency'),
})
export class Service {
constructor(private dep: string) {
}
}
As this tells Angular how to construct the service.
Is this all we need to do? I've seen other NPM modules like this one use ModuleWithProviders like this:
export class Module {
static forRoot(): ModuleWithProviders {
return {
ngModule: Module,
providers: [
StripeScriptTag
],
}
}
I assume that this is not necessary because the providers array still does not describe how to instantiate the service.
All you need is the
providedIn: 'root'
Services that are provided in root are tree shakeable. If it is provided in the root then you get the same singleton instance in the entire application. You only need to add it to a providers array if you want a new instance of the service across your module instead of the global singleton instance.

Set up Angular 6 environment variables from .env

There's an angular 6 project using environment variables from ./project/src/environments/environment.prod.ts
export const environment = {
production: true,
testVar: 'gg',
};
The backend for this project also has env variables in a .env file, so a lot of variable duplicate angular env variables. It would be nice to have something like
export const environment = {
production: true,
testVar: process.env.TEST_VAR
};
, so I didn't have to duplicate variables.
ie
I'd like to parse variables from a .env file and assign their values to angular env variables during typescript compilation on the server.
How can this be done?
Maybe with webpack?
UPDATE
Some clarification. My .env file contains no json. It looks like this:
TEST_VAR=1
UPDATE
Since ng eject is not available for Angular 6, I don't seem to be able to hack into webpack config. Looks like deadend here.
ng eject
Overview
Temporarily disabled.
Ejects your app and output the
proper webpack configuration and scripts.
This question becomes also more and more important, when we want to containerize angular applications.
My research lead me to an idea, where I have to write a little node.js or typescript program, using dotenv for reading .env file and create the environment.ts file at buildtime, before starting ng serve.
You can create entries in the package.json like this:
...
"config": "ts-node set-env.ts",
"server": "npm run config && ng serve"
...
and run it with
npm run server
Here is a good explanation with an example typescript file:
https://medium.com/#ferie/how-to-pass-environment-variables-at-building-time-in-an-angular-application-using-env-files-4ae1a80383c
You can create a config file and populate in Run-time.
1) create a File(app-config.json) in assets folder with your variables
{ "servicesUrl": "https://localhost:8080/api"}
2) create a service (AppConfigService ) to read the file.
#Injectable()
export class AppConfigService {
private appConfig;
constructor (private injector: Injector) { }
loadAppConfig() {
let http = this.injector.get(HttpClient);
return http.get('/assets/app-config.json')
.toPromise()
.then(data => {
this.appConfig = data;
})
}
get config() {
return this.appConfig;
}
3) Next we need to tell our application to execute the loadAppConfig() method of our service.
import { NgModule, APP_INITIALIZER } from '#angular/core';
import { AppConfigService } from './services/app-config.service';
#NgModule({
...,
providers: [
AppConfigService,
{
provide: APP_INITIALIZER,
useFactory: appInitializerFn,
multi: true,
deps: [AppConfigService]
}
],
...
})
export class AppModule { }
4) create a function called "appInitializerFn" to call our service in AppModule (app.module.ts)
const appInitializerFn = (appConfig: AppConfigService) => {
return () => {
return appConfig.loadAppConfig();
}
};
...
#NgModule({
...
})
export class AppModule {}
5) import environment and use it :example
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { AppConfigService } from './services/app-config.service';
#Injectable()
export class DataContextService {
basePath: string;
constructor (private environment: AppConfigService, private http: HttpClient) {
this.basePath = environment.config.servicesBasePath;
}
getNames() {
return this.http.get(this.basePath + '/names/');
}
}
for more information please see:
link
If you want to use variables in build time you could use dotenv
As early as possible in your application, require and configure dotenv.
require('dotenv').config()
Create a .env file in the root directory of your project. Add environment-specific variables on new lines in the form of NAME=VALUE. For example:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3

Angular2 dependency not injecting correct path

I am getting the following errors in my browser console, from trying to use localStorage with Angular2. It seems that the path it is generating isn't referring to the node_modules, but rather assuming that there is a localstorage.js in my site root (which there isn't). I am just referring to it normally (see my user.service below), so how do I get around this? All my other dependencies are working fine.
Error loading http://localhost:3000/localStorage.js as "localStorage" from http://localhost:3000/client/dev/user/services/user.service.js
import { Injectable } from 'angular2/core';
import { Headers } from 'angular2/http';
import { loalStorage } from 'localStorage';
#Injectable()
export class UserService {
private loggedIn = false;
constructor(private http: Http) {
this.loggedIn = !!localStorage.getItem('auth_token');
}
}
NB: I am fairly sure there isn't an actual problem with the localStorage installation, as if I run npm list localStorage, then it tells me I have localStorage#1.0.3 instaled.
If you want to use localStorage from an import, you need to configure it within SystemJS as described below:
System.config({
map: {
localStorage: 'node_modules/localStorage/lib/localStorage.js'
},
(...)
});
This way, you will be able to use the following import:
import loalStorage from 'localStorage';
See this question for more details since it's similar to the way to configure Lodash:
Lodash in angular2, declare var_:any not working

Categories

Resources