Definir constantes globales

258

En Angular 1.x puede definir constantes como esta:

angular.module('mainApp.config', [])
    .constant('API_ENDPOINT', 'http://127.0.0.1:6666/api/')

¿Cuál sería el equivalente en angular (con TypeScript)?

Simplemente no quiero repetir la URL base API una y otra vez en todos mis servicios.

AndreFeijo
fuente

Respuestas:

265

Los siguientes cambios me funcionan en la versión final de Angular 2:

export class AppSettings {
   public static API_ENDPOINT='http://127.0.0.1:6666/api/';
}

Y luego en el servicio:

import {Http} from 'angular2/http';
import {Message} from '../models/message';
import {Injectable} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {AppSettings} from '../appSettings';
import 'rxjs/add/operator/map';

@Injectable()
export class MessageService {

    constructor(private http: Http) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(AppSettings.API_ENDPOINT+'/messages')
            .map(response => response.json())
            .map((messages: Object[]) => {
                return messages.map(message => this.parseData(message));
            });
    }

    private parseData(data): Message {
        return new Message(data);
    }
}
AndreFeijo
fuente
Creo que tu AppSettingsclase debería ser abstracta y el API_ENDPOINTmiembro debería serlo readonly.
Philippe Gioseffi
164

La solución para la configuración proporcionada por el propio equipo angular se puede encontrar aquí. .

Aquí está todo el código relevante:

1) app.config.ts

import { OpaqueToken } from "@angular/core";

export let APP_CONFIG = new OpaqueToken("app.config");

export interface IAppConfig {
    apiEndpoint: string;
}

export const AppConfig: IAppConfig = {    
    apiEndpoint: "http://localhost:15422/api/"    
};

2) app.module.ts

import { APP_CONFIG, AppConfig } from './app.config';

@NgModule({
    providers: [
        { provide: APP_CONFIG, useValue: AppConfig }
    ]
})

3) your.service.ts

import { APP_CONFIG, IAppConfig } from './app.config';

@Injectable()
export class YourService {

    constructor(@Inject(APP_CONFIG) private config: IAppConfig) {
             // You can use config.apiEndpoint now
    }   
}

Ahora puede inyectar la configuración en todas partes sin usar los nombres de cadena y con el uso de su interfaz para verificaciones estáticas.

Por supuesto, puede separar la interfaz y la constante para poder suministrar diferentes valores en producción y desarrollo, por ejemplo

Ilya Chernomordik
fuente
3
Solo funciona cuando no especifico el tipo en el constructor del servicio. Entonces funciona cuando hago constructor (@Inject (APP_CONFIG) configuración privada) {} Aquí hay una mención de esto: blog.thoughtram.io/angular/2016/05/23/… pero no por qué.
Mukus
Supongo que se perdió alguna palabra clave de importación o exportación o algo así, ya que la uso con la interfaz y, como usted dice, es muy importante tenerla explícitamente escrita de forma estática. Quizás necesite proporcionar una excepción exacta aquí.
Ilya Chernomordik
46
Ninguna de estas soluciones, incluso el enfoque recomendado por el equipo angular parece elegante. ¿Por qué tratar de crear constantes es un proceso engorroso en Angular 2? ¿No puedes ver cuán transparente fue Angular1? ¿Por qué todo el desorden?
KhoPhi
31
Para cualquiera que llegue a esta respuesta, el OpaqueToken en Angular v4 está "en desuso" para InjectionToken - blog.thoughtram.io/angular/2016/05/23/…
mtpultz
3
¿Tendría sentido poner el código del Paso 1 en environment.tsy environment.prod.tspara que pueda tener diferentes constantes por entorno? @IlyaChernomordik comenzó a mencionar esto en el último párrafo de su respuesta.
Robert Bernstein
64

En Angular2, tiene la siguiente proporcionan definición, que le permite configurar diferentes tipos de dependencias:

provide(token: any, {useClass, useValue, useExisting, useFactory, deps, multi}

Comparando con Angular 1

app.serviceen Angular1 es equivalente a useClassen Angular2.

app.factoryen Angular1 es equivalente a useFactoryen Angular2.

app.constanty app.valuese ha simplificado useValuecon menos restricciones. es decir, ya no hay configbloque.

app.provider - No hay equivalente en Angular 2.

Ejemplos

Para configurar con el inyector raíz:

bootstrap(AppComponent,[provide(API_ENDPOINT, { useValue='http://127.0.0.1:6666/api/' })]);

O configure con el inyector de su componente:

providers: [provide(API_ENDPOINT, { useValue: 'http://127.0.0.1:6666/api/'})]

provide es mano corta para:

var injectorValue = Injector.resolveAndCreate([
  new Provider(API_ENDPOINT, { useValue: 'http://127.0.0.1:6666/api/'})
]);

Con el inyector, obtener el valor es fácil:

var endpoint = injectorValue.get(API_ENDPOINT);
bits de píxel
fuente
2
En realidad, me gustaría tener mi configuración en un archivo externo, por ejemplo: settings.ts ¿Cómo se vería este archivo?
AndreFeijo
¿Has considerado javascript del lado del servidor como NodeJS?
pixelbits el
55
Lo siento, no entendí cómo voy a inyectarlo en mi servicio. Como estoy usando un archivo externo, ¿necesito exportarlo?
AndreFeijo
Lo haría parte de su proceso de configuración de compilación. es decir, en función de su entorno, compile / empaquete diferentes archivos juntos y luego implemente. Todo esto lo puede hacer con NodeJS con los módulos adecuados.
pixelbits el
1
NodeJS no es una opción, desafortunadamente.
AndreFeijo
59

En Angular 4, puede usar la clase de entorno para mantener todos sus globales.

Tiene environment.ts y environment.prod.ts de forma predeterminada.

Por ejemplo

export const environment = {
  production: false,
  apiUrl: 'http://localhost:8000/api/'
};

Y luego en su servicio:

import { environment } from '../../environments/environment';
...
environment.apiUrl;
Nacho
fuente
Si usted está tratando de acceder a un constinterior de un servicio, puede que tenga que "proporcionar" se ordenaron los proveedores de su módulo de aplicación: { provide: 'ConstName', useValue: ConstName }. Estaba recibiendo un error de tiempo de ejecución sin esto.
daleyjem
@daleyjem eso es porque estabas tratando de inyectarlo. Este enfoque no utiliza el inyector
Aluan Haddad
Crear una constante como esta es la más simple. Supongo que el argumento contrario de perder DI y, por lo tanto, perder testability / mockValue es exagerado en algún momento. En una aplicación típica, utilizamos tantos componentes que no son DI como (RxJS) sin molestar la capacidad de prueba.
Amitesh
54

Actualizado para Angular 4+

Ahora podemos simplemente usar el archivo de entornos que angular proporciona valor predeterminado si su proyecto se genera a través de angular-cli.

por ejemplo

En su carpeta de entornos, cree los siguientes archivos

  • environment.prod.ts
  • environment.qa.ts
  • environment.dev.ts

y cada archivo puede contener cambios de código relacionados, tales como:

  • environment.prod.ts

    export const environment = {
         production: true,
         apiHost: 'https://api.somedomain.com/prod/v1/',
         CONSUMER_KEY: 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };
  • environment.qa.ts

    export const environment = {
         production: false,
         apiHost: 'https://api.somedomain.com/qa/v1/',
         CONSUMER_KEY : 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };
  • environment.dev.ts

    export const environment = {
         production: false,
         apiHost: 'https://api.somedomain.com/dev/v1/',
         CONSUMER_KEY : 'someReallyStupidTextWhichWeHumansCantRead', 
         codes: [ 'AB', 'AC', 'XYZ' ],
    };

Caso de uso en la aplicación

Puede importar entornos en cualquier archivo, como servicios clientUtilServices.ts

import {environment} from '../../environments/environment';

getHostURL(): string {
    return environment.apiHost;
  }

Caso de uso en construcción

Abra su archivo CLI angular .angular-cli.jsony "apps": [{...}]agregue el siguiente código

 "apps":[{
        "environments": {
            "dev": "environments/environment.ts",
            "prod": "environments/environment.prod.ts",
            "qa": "environments/environment.qa.ts",
           }
         }
       ]

Si desea compilar para producción, ejecútelo ng build --env=prodpara leer la configuración environment.prod.ts, de la misma manera que puede hacerlo qaodev

## Respuesta anterior

He estado haciendo algo como a continuación, en mi proveedor:

import {Injectable} from '@angular/core';

@Injectable()
export class ConstantService {

API_ENDPOINT :String;
CONSUMER_KEY : String;

constructor() {
    this.API_ENDPOINT = 'https://api.somedomain.com/v1/';
    this.CONSUMER_KEY = 'someReallyStupidTextWhichWeHumansCantRead'
  }
}

Entonces tengo acceso a todos los datos de Constant en cualquier lugar

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/map';

import {ConstantService} from  './constant-service'; //This is my Constant Service


@Injectable()
export class ImagesService {
    constructor(public http: Http, public ConstantService: ConstantService) {
    console.log('Hello ImagesService Provider');

    }

callSomeService() {

    console.log("API_ENDPOINT: ",this.ConstantService.API_ENDPOINT);
    console.log("CONSUMER_KEY: ",this.ConstantService.CONSUMER_KEY);
    var url = this.ConstantService.API_ENDPOINT;
    return this.http.get(url)
  }
 }
Anjum ...
fuente
66
Esto no funciona como una constante. El valor de una constante es siempre el mismo. En su caso, su API_ENDPOINTvalor puede sobrescribirse en cualquier momento. Si this.ConstantService.API_ENDPOINT = 'blah blah'se declara en la clase en cualquier momento después de que su llamada "constante" se importe desde constant-service, el nuevo valor de API_ENDPOINT sería 'blah blah'. Su solución solo muestra cómo acceder a una variable utilizando un servicio y no utilizando una constante.
Devner
1
@Devner solo hazlos de solo lecturareadonly API_ENDPOINT :String;
Flavien Volken
@Anjum Cómo angular selecciona los archivos env. ¿Debo pasar el nombre de env al iniciar la aplicación?
notionquest
@notionquest Sí, puedes pasarlo, comong build --env=prod
Anjum ...
31

Si bien el enfoque de tener una clase AppSettings con una cadena constante mientras ApiEndpoint funciona, no es ideal ya que no podríamos intercambiar este ApiEndpoint real por otros valores en el momento de la prueba unitaria.

Necesitamos poder inyectar estos puntos finales de la API en nuestros servicios (piense en inyectar un servicio en otro servicio). Tampoco necesitamos crear una clase completa para esto, todo lo que queremos hacer es inyectar una cadena en nuestros servicios siendo nuestro ApiEndpoint. Para completar la excelente respuesta por pixelbits , aquí está el código completo de cómo se puede hacer en Angular 2:

Primero, debemos decirle a Angular cómo proporcionar una instancia de nuestro ApiEndpoint cuando lo solicitamos en nuestra aplicación (piense en ello como el registro de una dependencia):

bootstrap(AppComponent, [
        HTTP_PROVIDERS,
        provide('ApiEndpoint', {useValue: 'http://127.0.0.1:6666/api/'})
]);         


Y luego, en el servicio que inyectamos este ApiEndpoint al constructor servicio y angular proporcionaremos por nosotros en base a nuestro registro anterior:

import {Http} from 'angular2/http';
import {Message} from '../models/message';
import {Injectable, Inject} from 'angular2/core';  // * We import Inject here
import {Observable} from 'rxjs/Observable';
import {AppSettings} from '../appSettings';
import 'rxjs/add/operator/map';

@Injectable()
export class MessageService {

    constructor(private http: Http, 
                @Inject('ApiEndpoint') private apiEndpoint: string) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(`${this.apiEndpoint}/messages`)
            .map(response => response.json())
            .map((messages: Object[]) => {
                return messages.map(message => this.parseData(message));
            });
    } 
    // the rest of the code...
}
Morteza Manavi
fuente
1
Ahora hay una forma "oficial" de recomendar el equipo angular en su tutorial. He agregado una respuesta a continuación: ( stackoverflow.com/a/40287063/1671558 )
Ilya Chernomordik
1
este código ya no es exacto, implementar esto causará un ApiEndpoint que no se encuentra en AppComponent.
WilliamX
Ok, entonces no estoy solo. ¿Sabes qué versión se rompió? ¿Hay alguna forma alternativa que no requiera definir valores en un objeto global y luego proporcionarlos?
Jens Bodal
29

Esta es mi experiencia reciente con este escenario:

  • @ angular / cli: 1.0.0
  • nodo: 6.10.2
  • @ angular / núcleo: 4.0.0

He seguido los documentos oficiales y actualizados aquí:

https://angular.io/docs/ts/latest/guide/dependency-injection.html#!#dependency-injection-tokens

Parece que OpaqueToken ahora está en desuso y debemos usar InjectionToken , por lo que estos son mis archivos que se ejecutan como un encanto:

app-config.interface.ts

export interface IAppConfig {

  STORE_KEY: string;

}

app-config.constants.ts

import { InjectionToken } from "@angular/core";
import { IAppConfig } from "./app-config.interface";

export const APP_DI_CONFIG: IAppConfig = {

  STORE_KEY: 'l@_list@'

};

export let APP_CONFIG = new InjectionToken< IAppConfig >( 'app.config' );

app.module.ts

import { APP_CONFIG, APP_DI_CONFIG } from "./app-config/app-config.constants";

@NgModule( {
  declarations: [ ... ],
  imports: [ ... ],
  providers: [
    ...,
    {
      provide: APP_CONFIG,
      useValue: APP_DI_CONFIG
    }
  ],
  bootstrap: [ ... ]
} )
export class AppModule {}

my-service.service.ts

  constructor( ...,
               @Inject( APP_CONFIG ) private config: IAppConfig) {

    console.log("This is the App's Key: ", this.config.STORE_KEY);
    //> This is the App's Key:  l@_list@

  }

El resultado es limpio y no hay advertencias en la consola, gracias al reciente comentario de John Papa en este número:

https://github.com/angular/angular-cli/issues/2034

La clave fue implementar en un archivo diferente la interfaz.

JavierFuentes
fuente
véase también stackoverflow.com/a/43193574/3092596 - que es básicamente el mismo, pero crea módulos inyectables en lugar de proveedores
goredwards
19

Todas las soluciones parecen ser complicadas. Estoy buscando la solución más simple para este caso y solo quiero usar constantes. Las constantes son simples. ¿Hay algo que hable en contra de la siguiente solución?

app.const.ts

'use strict';

export const dist = '../path/to/dist/';

app.service.ts

import * as AppConst from '../app.const'; 

@Injectable()
export class AppService {

    constructor (
    ) {
        console.log('dist path', AppConst.dist );
    }

}
Alexander Schmidt
fuente
2
Bueno, está utilizando variables fuera del alcance del servicio, por lo que también podría usar globals de ventana. Lo que estamos tratando de hacer es introducir constantes en el sistema de inyección de dependencia Angular4 para que podamos mantener el alcance limpio, stubable o burlable.
Joel Hernández el
11

Simplemente use una constante de Typecript

export var API_ENDPOINT = 'http://127.0.0.1:6666/api/';

Puede usarlo en el inyector de dependencia usando

bootstrap(AppComponent, [provide(API_ENDPOINT, {useValue: 'http://127.0.0.1:6666/api/'}), ...]);
SnareChops
fuente
1
¿Por qué inyectarlo? No hay necesidad de eso, creo ... puedes usarlo tan pronto como lo importes. @SnareChops
Sasxa
@ Sasxa Estoy de acuerdo, aunque podría ser bueno para pruebas unitarias y tal. Solo trato de dar una respuesta completa.
SnareChops
1
@Andreas Usted podría utilizar constyest
SnareChops
Proporcione un stackblitz de este trabajo. He visto muchos ejemplos de proporcionar un servicio en el método bootstrap, pero aún no he encontrado uno con un ejemplo que funcione suficientemente. Posiblemente algo ha cambiado en una versión más reciente de angular.
Jens Bodal
4

Si estás usando Webpack , que recomiendo, puede configurar constantes para diferentes entornos. Esto es especialmente valioso cuando tiene valores constantes diferentes para cada entorno.

Es probable que tenga varios archivos webpack en su /configdirectorio (por ejemplo, webpack.dev.js, webpack.prod.js, etc.). Entonces tendrás uncustom-typings.d.ts agregarás allí. Aquí está el patrón general a seguir en cada archivo y un uso de muestra en un Componente.

paquete web. {env} .js

const API_URL = process.env.API_URL = 'http://localhost:3000/';
const JWT_TOKEN_NAME = "id_token";
...
    plugins: [
      // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts
      new DefinePlugin({
        'API_URL': JSON.stringify(API_URL),
        'JWT_TOKEN_NAME': JSON.stringify(JWT_TOKEN_NAME)
      }),

custom-typings.d.ts

declare var API_URL: string;
declare var JWT_TOKEN_NAME: string;
interface GlobalEnvironment {
  API_URL: string;
  JWT_TOKEN_NAME: string;
}

Componente

export class HomeComponent implements OnInit {
  api_url:string = API_URL;
  authToken: string = "Bearer " + localStorage.getItem(JWT_TOKEN_NAME)});
}
occasl
fuente
3

Usar un archivo de propiedades que se genera durante una compilación es simple y fácil. Este es el enfoque que utiliza la CLI angular. Defina un archivo de propiedades para cada entorno y use un comando durante la compilación para determinar qué archivo se copia en su aplicación. Luego, simplemente importe el archivo de propiedades para usar.

https://github.com/angular/angular-cli#build-targets-and-environment-files

R.Creager
fuente
3

Un enfoque para Angular4 sería definir una constante a nivel de módulo:

const api_endpoint = 'http://127.0.0.1:6666/api/';

@NgModule({
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: [
    MessageService,
    {provide: 'API_ENDPOINT', useValue: api_endpoint}
  ]
})
export class AppModule {
}

Entonces, a su servicio:

import {Injectable, Inject} from '@angular/core';

@Injectable()
export class MessageService {

    constructor(private http: Http, 
      @Inject('API_ENDPOINT') private api_endpoint: string) { }

    getMessages(): Observable<Message[]> {
        return this.http.get(this.api_endpoint+'/messages')
            .map(response => response.json())
            .map((messages: Object[]) => {
                return messages.map(message => this.parseData(message));
            });
    }

    private parseData(data): Message {
        return new Message(data);
    }
}
Juangui Jordán
fuente
3

Tengo otra forma de definir constantes globales. Porque si definimos en el archivo ts, si compilamos en modo de producción, no es fácil encontrar constantes para cambiar el valor.

export class SettingService  {

  constructor(private http: HttpClient) {

  }

  public getJSON(file): Observable<any> {
      return this.http.get("./assets/configs/" + file + ".json");
  }
  public getSetting(){
      // use setting here
  }
}

En la carpeta de la aplicación, agrego la carpeta configs / setting.json

Contenido en setting.json

{
    "baseUrl": "http://localhost:52555"
}

En el módulo de la aplicación, agregue APP_INITIALIZER

   {
      provide: APP_INITIALIZER,
      useFactory: (setting: SettingService) => function() {return setting.getSetting()},
      deps: [SettingService],
      multi: true
    }

De esta manera, puedo cambiar el valor en el archivo json más fácilmente. También lo uso para mensajes de error / advertencia constantes.

Hien Nguyen
fuente
0

AngularJS's module.constant no define una constante en el sentido estándar.

Si bien se destaca como mecanismo de registro de proveedores, se entiende mejor en el contexto de la función relacionada module.value( $provide.value). La documentación oficial establece claramente el caso de uso:

Registre un servicio de valor con el inyector $, como una cadena, un número, una matriz, un objeto o una función. Esto es la abreviatura de registrar un servicio donde la propiedad $ get de su proveedor es una función de fábrica que no toma argumentos y devuelve el servicio de valor. Eso también significa que no es posible inyectar otros servicios en un servicio de valor.

Compare esto con la documentación de module.constant( $provide.constant) que también establece claramente el caso de uso (énfasis mío):

Registre un servicio constante con el inyector $, como una cadena, un número, una matriz, un objeto o una función. Al igual que el valor, no es posible inyectar otros servicios en una constante. Pero a diferencia del valor, una constante puede inyectarse en una función de configuración del módulo (ver angular.Module) y no puede ser anulada por un decorador AngularJS .

Por lo tanto, la constantfunción AngularJS no proporciona una constante en el significado comúnmente entendido del término en el campo.

Dicho esto, las restricciones impuestas al objeto proporcionado, junto con su disponibilidad anterior a través del inyector $, sugieren claramente que el nombre se usa por analogía.

Si quisiera una constante real en una aplicación AngularJS, "proporcionaría" una de la misma manera que lo haría en cualquier programa JavaScript que sea

export const π = 3.14159265;

En Angular 2, se aplica la misma técnica.

Las aplicaciones de Angular 2 no tienen una fase de configuración en el mismo sentido que las aplicaciones de AngularJS. Además, no existe un mecanismo de decorador de servicios ( AngularJS Decorator ), pero esto no es particularmente sorprendente dado lo diferentes que son entre sí.

El ejemplo de

angular
  .module('mainApp.config', [])
  .constant('API_ENDPOINT', 'http://127.0.0.1:6666/api/');

es vagamente arbitrario y ligeramente desagradable porque $provide.constantse usa para especificar un objeto que, por cierto, también es una constante. También podrías haber escrito

export const apiEndpoint = 'http://127.0.0.1:6666/api/';

para todos tampoco puede cambiar.

Ahora el argumento a favor de la capacidad de prueba, burlándose de la constante, disminuye porque literalmente no cambia.

Uno no se burla de π.

Por supuesto, la semántica específica de su aplicación podría ser que su punto final podría cambiar, o su API podría tener un mecanismo de conmutación por error no transparente, por lo que sería razonable que el punto final de la API cambie bajo ciertas circunstancias.

Pero en ese caso, proporcionarlo como una representación literal de cadena de una única URL a la constantfunción no habría funcionado.

Un mejor argumento, y probablemente uno más alineado con la razón de la existencia de la $provide.constantfunción AngularJS es que, cuando se introdujo AngularJS, JavaScript no tenía concepto de módulo estándar . En ese caso, los globales se usarían para compartir valores, mutables o inmutables, y el uso de globales es problemático.

Dicho esto, proporcionar algo como esto a través de un marco aumenta el acoplamiento a ese marco. También combina lógica angular específica con lógica que funcionaría en cualquier otro sistema.

Esto no quiere decir que sea un enfoque incorrecto o dañino, pero personalmente, si quiero una constante en una aplicación Angular 2, escribiré

export const π = 3.14159265;

tal como lo hubiera hecho si estuviera usando AngularJS.

Los más cambian las cosas...

Aluan Haddad
fuente
0

La mejor manera de crear constantes de aplicación amplia en Angular 2 es mediante el uso de archivos environment.ts. La ventaja de declarar tales constantes es que puede variarlas según el entorno, ya que puede haber un archivo de entorno diferente para cada entorno.

Hassan Arafat
fuente
Esto no funciona si tiene la intención de construir su aplicación una vez y luego implementarla en múltiples entornos.
Jens Bodal
-1

Puede crear una clase para su variable global y luego exportar esta clase así:

export class CONSTANT {
    public static message2 = [
        { "NAME_REQUIRED": "Name is required" }
    ]

    public static message = {
        "NAME_REQUIRED": "Name is required",
    }
}

Después de crear y exportar su CONSTANTclase, debe importar esta clase en la clase que desea usar, de esta manera:

import { Component, OnInit                       } from '@angular/core';
import { CONSTANT                                } from '../../constants/dash-constant';


@Component({
  selector   : 'team-component',
  templateUrl: `../app/modules/dashboard/dashComponents/teamComponents/team.component.html`,
})

export class TeamComponent implements OnInit {
  constructor() {
    console.log(CONSTANT.message2[0].NAME_REQUIRED);
    console.log(CONSTANT.message.NAME_REQUIRED);
  }

  ngOnInit() {
    console.log("oninit");
    console.log(CONSTANT.message2[0].NAME_REQUIRED);
    console.log(CONSTANT.message.NAME_REQUIRED);
  }
}

Puede usar esto en constructoro ngOnInit(){}, o en cualquier método predefinido.

Shubham Verma
fuente