Definir constantes globais

258

No Angular 1.x, você pode definir constantes assim:

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

Qual seria o equivalente em Angular (com TypeScript)?

Só não quero repetir o URL base da API repetidamente em todos os meus serviços.

AndreFeijo
fonte

Respostas:

265

As alterações abaixo funcionam para mim na versão final do Angular 2:

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

E então no serviço:

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
fonte
Eu acho que sua AppSettingsclasse deve ser abstrata e API_ENDPOINTmembro deve ser readonly.
Philippe Gioseffi
164

A solução para a configuração fornecida pela própria equipe angular pode ser encontrada aqui .

Aqui está todo o 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
    }   
}

Agora você pode injetar a configuração em qualquer lugar sem usar os nomes das strings e com o uso da sua interface para verificações estáticas.

Obviamente, é possível separar a interface e a constante para poder fornecer valores diferentes na produção e desenvolvimento, por exemplo

Ilya Chernomordik
fonte
3
Funciona apenas quando não especifico o tipo no construtor do serviço. Então funciona quando eu faço o construtor (@Inject (APP_CONFIG) configuração privada) {} há uma menção a isso aqui: blog.thoughtram.io/angular/2016/05/23/…, mas não o porquê.
Mukus
Suponho que você tenha perdido alguma palavra-chave de importação ou exportação ou algo parecido, pois eu a uso com a interface e é como você diz muito importante que seja explicitamente digitada estaticamente. Talvez você precise fornecer a exceção exata aqui.
Ilya Chernomordik
46
Nenhuma dessas soluções, mesmo a abordagem recomendada pela equipe angular, parece elegante. Por que tentar criar constantes é um processo complicado no Angular 2? Você não vê como o Angular1 foi perfeito? Por que toda essa bagunça?
KhoPhi
31
Para qualquer outra pessoa que acerta essa resposta, o OpaqueToken no Angular v4 é "descontinuado" para o InjectionToken - blog.thoughtram.io/angular/2016/05/23/…
mtpultz 26/17
3
Faria sentido inserir o código da Etapa 1 environment.tse environment.prod.tspoder ter constantes diferentes por ambiente? @IlyaChernomordik começou a mencionar isso no último parágrafo de sua resposta.
22817 Robert Bernstein
64

No Angular2, você tem a seguinte definição de fornecimento , que permite configurar diferentes tipos de dependências:

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

Comparando com Angular 1

app.serviceem Angular1 é equivalente a useClassem Angular2.

app.factoryem Angular1 é equivalente a useFactoryem Angular2.

app.constante app.valuefoi simplificado useValuecom menos restrições. ou seja, não há configmais bloqueio.

app.provider - Não há equivalente no Angular 2.

Exemplos

Para configurar com o injetor raiz:

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

Ou configure com o injetor do seu componente:

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

provide é mão curta para:

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

Com o injetor, é fácil obter o valor:

var endpoint = injectorValue.get(API_ENDPOINT);
pixelbits
fonte
2
Na verdade, eu gostaria de ter minhas configurações em um arquivo externo, por exemplo: settings.ts Como esse arquivo seria?
precisa saber é o seguinte
Você considerou o javascript do servidor, como o NodeJS?
pixelbits
5
Desculpe, eu não entendi como vou injetá-lo no meu serviço? Como estou usando um arquivo externo, preciso exportá-lo?
precisa saber é o seguinte
Eu faria parte do seu processo de configuração da compilação. ou seja, com base no seu ambiente, compile / empacote arquivos diferentes e implante. Tudo isso você pode fazer com o NodeJS com os módulos adequados.
pixelbits
1
Infelizmente, o NodeJS não é uma opção.
AndreFeijo 25/01
59

No Angular 4, você pode usar a classe de ambiente para manter todos os seus globais.

Você possui environment.ts e environment.prod.ts por padrão.

Por exemplo

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

E depois no seu serviço:

import { environment } from '../../environments/environment';
...
environment.apiUrl;
Nacho
fonte
Se você está tentando acessar um constdentro de um serviço, você pode ter que "fornecer"-lo em ordem de provedores de seu módulo app: { provide: 'ConstName', useValue: ConstName }. Eu estava recebendo um erro de tempo de execução sem isso.
daleyjem
@daleyjem é porque você estava tentando injetar. Esta abordagem não usa o injetor
Aluan Haddad
Criar uma constante como essa é a mais simples. Eu acho que o contra-argumento de perder DI e, assim, perder testability / mockValue é exagerado. No aplicativo típico, usamos tantos componentes não DI como (RxJS) sem incomodar a testabilidade.
Amitesh
54

Atualizado para Angular 4+

Agora podemos simplesmente usar o arquivo de ambientes que angular fornece o padrão se o seu projeto é gerado via angular-cli.

por exemplo

Na pasta de ambientes, crie os seguintes arquivos

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

e cada arquivo pode conter alterações de código relacionadas, 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 no aplicativo

Você pode importar ambientes para qualquer arquivo, como serviços clientUtilServices.ts

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

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

Caso de uso na construção

Abra seu arquivo cli angular .angular-cli.jsone, dentro, "apps": [{...}]adicione o seguinte código

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

Se você deseja criar para produção, executá- ng build --env=prodlo lerá a configuração environment.prod.ts, da mesma maneira que você pode fazê-lo para qaoudev

## Resposta mais antiga

Eu tenho feito algo como abaixo, no meu provedor:

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'
  }
}

Então eu tenho acesso a todos os dados constantes em qualquer 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 ....
fonte
6
Isso não funciona como uma constante. O valor de uma constante é sempre o mesmo. No seu caso, seu API_ENDPOINTvalor pode ser sobrescrito a qualquer momento. Se this.ConstantService.API_ENDPOINT = 'blah blah'for declarado na classe a qualquer momento após a importação da chamada "constante" constant-service, o novo valor de API_ENDPOINT seria 'blah blah'. Sua solução mostra apenas como acessar uma variável usando um serviço e não usando uma constante.
Devner
1
@Devner apenas torná-los somente leiturareadonly API_ENDPOINT :String;
Flavien Volken
@Anjum Como angular seleciona os arquivos env. Devo passar o nome do env ao iniciar o aplicativo?
Notionquest
@notionquest Sim, você pode passá-lo, comong build --env=prod
Anjum ....
31

Embora a abordagem de ter uma classe AppSettings com uma constante de seqüência de caracteres como o ApiEndpoint funcione, não é ideal, pois não poderíamos trocar esse ApiEndpoint real por outros valores no momento do teste de unidade.

Precisamos ser capazes de injetar esses pontos de extremidade da API em nossos serviços (pense em injetar um serviço em outro serviço). Também não precisamos criar uma classe inteira para isso, tudo o que queremos fazer é injetar uma string em nossos serviços, sendo nosso ApiEndpoint. Para concluir a excelente resposta de pixelbits , aqui está o código completo de como isso pode ser feito no Angular 2:

Primeiro, precisamos dizer à Angular como fornecer uma instância do nosso ApiEndpoint quando a solicitarmos em nosso aplicativo (pense nisso como registrando uma dependência):

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


E então, no serviço, injetamos esse ApiEndpoint no construtor de serviços e a Angular o fornecerá com base em nosso registro acima:

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
fonte
1
Agora existe uma maneira "oficial" de fazer recomendações pela equipe angular em seu tutorial. Eu adicionei uma resposta abaixo: ( stackoverflow.com/a/40287063/1671558 )
Ilya Chernomordik
1
esse código não é mais preciso, a implementação disso fará com que um ApiEndpoint não seja encontrado no AppComponent.
WilliamX
Ok, então eu não estou sozinha. Você sabe qual versão isso quebrou? Existe uma maneira alternativa que não exija definir valores em um objeto global e depois fornecê-los?
Jens Bodal #
29

Esta é minha experiência recente com este cenário:

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

Segui os documentos oficiais e atualizados aqui:

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

Parece que o OpaqueToken agora está obsoleto e devemos usar o InjectionToken ; portanto, esses são meus arquivos executados como um 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@

  }

O resultado é limpo e não há avisos no console, graças ao recente comentário de John Papa nesta edição:

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

A chave foi implementada em um arquivo diferente da interface.

JavierFuentes
fonte
ver também stackoverflow.com/a/43193574/3092596 - que é basicamente o mesmo, mas cria módulos injetáveis ao invés de provedores
goredwards
19

Todas as soluções parecem ser complicadas. Estou procurando a solução mais simples para este caso e só quero usar constantes. As constantes são simples. Existe algo que fale contra a seguinte solução?

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
fonte
2
Bem, você está usando variáveis ​​fora do escopo do serviço para poder usar apenas globals da janela. O que estamos tentando fazer é obter constantes no sistema de injeção de dependência do Angular4, para que possamos manter o escopo limpo, stubable ou zombável.
Joel Hernandez
11

Basta usar uma constante Typescript

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

Você pode usá-lo no injetor de dependência usando

bootstrap(AppComponent, [provide(API_ENDPOINT, {useValue: 'http://127.0.0.1:6666/api/'}), ...]);
SnareChops
fonte
1
Por que injetar? Eu acho que não há necessidade ... você pode usá-lo assim que importá-lo. @SnareChops
Sasxa
@ Sasxa Eu concordo, embora possa ser bom para testes de unidade e tal. Apenas tentando fornecer uma resposta completa.
SnareChops
1
@Andreas Você poderia usar constyest
SnareChops
Forneça um stackblitz deste trabalho. Eu já vi muitos exemplos de prestação de serviço no método de inicialização, mas ainda não encontrei um com um exemplo suficientemente funcional. Possivelmente algo mudou em uma versão mais recente do angular.
Jens Bodal #
4

Se você estiver usando o Webpack , que eu recomendo, você pode configurar constantes para diferentes ambientes. Isso é especialmente valioso quando você tem diferentes valores constantes em uma base por ambiente.

Você provavelmente terá vários arquivos webpack em seu /configdiretório (por exemplo, webpack.dev.js, webpack.prod.js, etc.). Então você terá um, custom-typings.d.tsvocê os adicionará lá. Aqui está o padrão geral a seguir em cada arquivo e um exemplo de uso em um Componente.

webpack. {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)});
}
ocasional
fonte
3

O uso de um arquivo de propriedades gerado durante uma construção é simples e fácil. Essa é a abordagem que a CLI Angular usa. Defina um arquivo de propriedades para cada ambiente e use um comando durante a compilação para determinar qual arquivo será copiado para seu aplicativo. Em seguida, basta importar o arquivo de propriedades a ser usado.

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

R.Creager
fonte
3

Uma abordagem para o Angular4 seria definir uma constante no nível do 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 {
}

Então, em seu serviço:

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
fonte
3

Eu tenho outra maneira de definir constantes globais. Porque se definimos no arquivo ts, se construímos no modo de produção, não é fácil encontrar constantes para alterar o 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
  }
}

Na pasta do aplicativo, adiciono a pasta configs / setting.json

Conteúdo em setting.json

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

No módulo de aplicativo, adicione APP_INITIALIZER

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

Dessa forma, eu posso alterar o valor no arquivo json mais facilmente. Eu também uso esse caminho para mensagens constantes de erro / aviso.

Hien Nguyen
fonte
0

O AngularJS module.constantnão define uma constante no sentido padrão.

Embora ele próprio seja um mecanismo de registro de provedor, ele é melhor compreendido no contexto da função related module.value( $provide.value). A documentação oficial indica claramente o caso de uso:

Registre um serviço de valor com o injetor $, como uma string, um número, uma matriz, um objeto ou uma função. É a abreviação de registrar um serviço em que a propriedade $ get do provedor é uma função de fábrica que não aceita argumentos e retorna o serviço de valor. Isso também significa que não é possível injetar outros serviços em um serviço de valor.

Compare isso com a documentação de module.constant( $provide.constant), que também indica claramente o caso de uso (ênfase meu):

Registre um serviço constante com o injetor $, como uma string, um número, uma matriz, um objeto ou uma função. Como o valor, não é possível injetar outros serviços em uma constante. Porém, diferentemente do valor, uma constante pode ser injetada em uma função de configuração do módulo (consulte angular.Module) e não pode ser substituída por um decorador do AngularJS .

Portanto, a constantfunção AngularJS não fornece uma constante no significado comumente entendido do termo no campo.

Dito isto, as restrições impostas ao objeto fornecido, juntamente com sua disponibilidade anterior via injetor $, sugerem claramente que o nome é usado por analogia.

Se você quisesse uma constante real em um aplicativo AngularJS, "forneceria" uma da mesma maneira que faria em qualquer programa JavaScript que seja

export const π = 3.14159265;

No Angular 2, a mesma técnica é aplicável.

Os aplicativos Angular 2 não possuem uma fase de configuração no mesmo sentido que os aplicativos AngularJS. Além disso, não há mecanismo de decorador de serviço ( AngularJS Decorator ), mas isso não é particularmente surpreendente, considerando a diferença entre eles.

O exemplo de

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

é vagamente arbitrário e um pouco desanimador, porque $provide.constantestá sendo usado para especificar um objeto que, aliás, também é uma constante. Você também pode ter escrito

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

pois todos podem mudar.

Agora, o argumento da testabilidade, zombando da constante, diminui porque literalmente não muda.

Não se zomba de π.

É claro que a semântica específica de seu aplicativo pode ser que seu terminal possa mudar ou sua API possa ter um mecanismo de failover não transparente, portanto, seria razoável que o terminal da API fosse alterado sob determinadas circunstâncias.

Mas, nesse caso, fornecê-lo como uma representação literal de cadeia de caracteres de um único URL para a constantfunção não teria funcionado.

Um argumento melhor e provavelmente mais um alinhado com a razão da existência da $provide.constantfunção AngularJS é que, quando o AngularJS foi introduzido, o JavaScript não tinha um conceito de módulo padrão . Nesse caso, os globais seriam usados ​​para compartilhar valores, mutáveis ​​ou imutáveis, e o uso de globais é problemático.

Dito isto, fornecer algo assim através de uma estrutura aumenta o acoplamento a essa estrutura. Também combina lógica específica angular com lógica que funcionaria em qualquer outro sistema.

Isso não quer dizer que seja uma abordagem errada ou prejudicial, mas pessoalmente, se eu quiser uma constante em um aplicativo Angular 2, escreverei

export const π = 3.14159265;

exatamente como eu teria usado o AngularJS.

Quanto mais as coisas mudam ...

Aluan Haddad
fonte
0

A melhor maneira de criar constantes amplas para aplicativos no Angular 2 é usando os arquivos environment.ts. A vantagem de declarar essas constantes é que você pode variar de acordo com o ambiente, pois pode haver um arquivo de ambiente diferente para cada ambiente.

Hassan Arafat
fonte
Isso não funciona se você pretende criar seu aplicativo uma vez e depois implantá-lo em vários ambientes.
Jens Bodal #
-1

Você pode criar uma classe para sua variável global e exportar essa classe da seguinte maneira:

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

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

Depois de criar e exportar sua CONSTANTclasse, você deve importar essa classe na classe em que deseja usar, assim:

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);
  }
}

Você pode usar isso em constructorou ngOnInit(){}ou em qualquer método predefinido.

Shubham Verma
fonte