diff --git a/adev-es/src/app/routing/sub-navigation-data.ts b/adev-es/src/app/routing/sub-navigation-data.ts index 02ca72c..46be2c0 100644 --- a/adev-es/src/app/routing/sub-navigation-data.ts +++ b/adev-es/src/app/routing/sub-navigation-data.ts @@ -298,13 +298,13 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [ status: 'updated', }, { - label: 'Creating and using services', + label: 'Crando y usando servicios', path: 'guide/di/creating-and-using-services', contentPath: 'guide/di/creating-and-using-services', status: 'updated', }, { - label: 'Defining dependency providers', + label: 'Definiendo proveedores de dependencias', path: 'guide/di/defining-dependency-providers', contentPath: 'guide/di/defining-dependency-providers', status: 'updated', diff --git a/adev-es/src/content/guide/di/creating-and-using-services.en.md b/adev-es/src/content/guide/di/creating-and-using-services.en.md new file mode 100644 index 0000000..f8df513 --- /dev/null +++ b/adev-es/src/content/guide/di/creating-and-using-services.en.md @@ -0,0 +1,105 @@ +# Creating and using services + +Services are reusable pieces of code that can be shared across your Angular application. They typically handle data fetching, business logic, or other functionality that multiple components need to access. + +## Creating a service + +You can create a service with the [Angular CLI](tools/cli) with the following command: + +```bash +ng generate service CUSTOM_NAME +``` + +This creates a dedicated `CUSTOM_NAME.ts` file in your `src` directory. + +You can also manually create a service by adding the `@Injectable()` decorator to a TypeScript class. This tells Angular that the service can be injected as a dependency. + +Here is an example of a service that allows users to add and request data: + +```ts +// 📄 src/app/basic-data-store.ts +import { Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class BasicDataStore { + private data: string[] = [] + + addData(item: string): void { + this.data.push(item) + } + + getData(): string[] { + return [...this.data] + } +} +``` + +## How services become available + +When you use `@Injectable({ providedIn: 'root' })` in your service, Angular: + +- **Creates a single instance** (singleton) for your entire application +- **Makes it available everywhere** without any additional configuration +- **Enables tree-shaking** so the service is only included in your JavaScript bundle if it's actually used + +This is the recommended approach for most services. + +## Injecting a service + +Once you've created a service with `providedIn: 'root'`, you can inject it anywhere in your application using the `inject()` function from `@angular/core`. + +### Injecting into a component + +```angular-ts +import { Component, inject } from '@angular/core'; +import { BasicDataStore } from './basic-data-store'; + +@Component({ + selector: 'app-example', + template: ` +
+

{{ dataStore.getData() }}

+ +
+ ` +}) +export class ExampleComponent { + dataStore = inject(BasicDataStore); +} +``` + +### Injecting into another service + +```ts +import { inject, Injectable } from '@angular/core'; +import { AdvancedDataStore } from './advanced-data-store'; + +@Injectable({ + providedIn: 'root', +}) +export class BasicDataStore { + private advancedDataStore = inject(AdvancedDataStore); + private data: string[] = []; + + addData(item: string): void { + this.data.push(item); + } + + getData(): string[] { + return [...this.data, ...this.advancedDataStore.getData()]; + } +} +``` + +## Next steps + +While `providedIn: 'root'` covers most use cases, Angular offers additional ways to provide services for specialized scenarios: + +- **Component-specific instances** - When components need their own isolated service instances +- **Manual configuration** - For services that require runtime configuration +- **Factory providers** - For dynamic service creation based on runtime conditions +- **Value providers** - For providing configuration objects or constants + +You can learn more about these advanced patterns in the next guide: [defining dependency providers](/guide/di/defining-dependency-providers). diff --git a/adev-es/src/content/guide/di/creating-and-using-services.md b/adev-es/src/content/guide/di/creating-and-using-services.md index f8df513..0ad4e8e 100644 --- a/adev-es/src/content/guide/di/creating-and-using-services.md +++ b/adev-es/src/content/guide/di/creating-and-using-services.md @@ -1,20 +1,20 @@ -# Creating and using services +# Creando y usando servicios -Services are reusable pieces of code that can be shared across your Angular application. They typically handle data fetching, business logic, or other functionality that multiple components need to access. +Los servicios son piezas de código reutilizables que pueden compartirse a través de tu aplicación Angular. Típicamente manejan la obtención de datos, lógica de negocio u otra funcionalidad que múltiples componentes necesitan acceder. -## Creating a service +## Creando un servicio -You can create a service with the [Angular CLI](tools/cli) with the following command: +Puedes crear un servicio con el [Angular CLI](tools/cli) con el siguiente comando: ```bash ng generate service CUSTOM_NAME ``` -This creates a dedicated `CUSTOM_NAME.ts` file in your `src` directory. +Esto crea un archivo dedicado `CUSTOM_NAME.ts` en tu directorio `src`. -You can also manually create a service by adding the `@Injectable()` decorator to a TypeScript class. This tells Angular that the service can be injected as a dependency. +También puedes crear manualmente un servicio añadiendo el decorador `@Injectable()` a una clase TypeScript. Esto le indica a Angular que el servicio puede ser inyectado como una dependencia. -Here is an example of a service that allows users to add and request data: +Aquí hay un ejemplo de un servicio que permite a los usuarios agregar y solicitar datos: ```ts // 📄 src/app/basic-data-store.ts @@ -34,21 +34,21 @@ export class BasicDataStore { } ``` -## How services become available +## Cómo los servicios se vuelven disponibles -When you use `@Injectable({ providedIn: 'root' })` in your service, Angular: +Cuando usas `@Injectable({ providedIn: 'root' })` en tu servicio, Angular: -- **Creates a single instance** (singleton) for your entire application -- **Makes it available everywhere** without any additional configuration -- **Enables tree-shaking** so the service is only included in your JavaScript bundle if it's actually used +- **Crea una única instancia** (singleton) para toda tu aplicación +- **Lo hace disponible en todas partes** sin ninguna configuración adicional +- **Habilita tree-shaking** para que el servicio solo se incluya en tu bundle de JavaScript si realmente se usa -This is the recommended approach for most services. +Este es el enfoque recomendado para la mayoría de los servicios. -## Injecting a service +## Inyectando un servicio -Once you've created a service with `providedIn: 'root'`, you can inject it anywhere in your application using the `inject()` function from `@angular/core`. +Una vez que has creado un servicio con `providedIn: 'root'`, puedes inyectarlo en cualquier parte de tu aplicación usando la función `inject()` de `@angular/core`. -### Injecting into a component +### Inyectando en un componente ```angular-ts import { Component, inject } from '@angular/core'; @@ -70,7 +70,7 @@ export class ExampleComponent { } ``` -### Injecting into another service +### Inyectando en otro servicio ```ts import { inject, Injectable } from '@angular/core'; @@ -93,13 +93,13 @@ export class BasicDataStore { } ``` -## Next steps +## Próximos pasos -While `providedIn: 'root'` covers most use cases, Angular offers additional ways to provide services for specialized scenarios: +Aunque `providedIn: 'root'` cubre la mayoría de los casos de uso, Angular ofrece formas adicionales de proveer servicios para escenarios especializados: -- **Component-specific instances** - When components need their own isolated service instances -- **Manual configuration** - For services that require runtime configuration -- **Factory providers** - For dynamic service creation based on runtime conditions -- **Value providers** - For providing configuration objects or constants +- **Instancias específicas de componente** - Cuando los componentes necesitan sus propias instancias aisladas de servicio +- **Configuración manual** - Para servicios que requieren configuración en tiempo de ejecución +- **Proveedores factory** - Para creación dinámica de servicios basada en condiciones de tiempo de ejecución +- **Proveedores de valor** - Para proveer objetos de configuración o constantes -You can learn more about these advanced patterns in the next guide: [defining dependency providers](/guide/di/defining-dependency-providers). +Puedes aprender más sobre estos patrones avanzados en la siguiente guía: [definiendo proveedores de dependencias](/guide/di/defining-dependency-providers). diff --git a/adev-es/src/content/guide/di/defining-dependency-providers.en.md b/adev-es/src/content/guide/di/defining-dependency-providers.en.md new file mode 100644 index 0000000..3702715 --- /dev/null +++ b/adev-es/src/content/guide/di/defining-dependency-providers.en.md @@ -0,0 +1,923 @@ +# Defining dependency providers + +Angular provides two ways to make services available for injection: + +1. **Automatic provision** - Using `providedIn` in the `@Injectable` decorator or by providing a factory in the `InjectionToken` configuration +2. **Manual provision** - Using the `providers` array in components, directives, routes, or application config + +In the [previous guide](/guide/di/creating-and-using-services), you learned how to create services using `providedIn: 'root'`, which handles most common use cases. This guide explores additional patterns for both automatic and manual provider configuration. + +## Automatic provision for non-class dependencies + +While the `@Injectable` decorator with `providedIn: 'root'` works great for services (classes), you might need to provide other types of values globally - like configuration objects, functions, or primitive values. Angular provides `InjectionToken` for this purpose. + +### What is an InjectionToken? + +An `InjectionToken` is an object that Angular's dependency injection system uses to uniquely identify values for injection. Think of it as a special key that lets you store and retrieve any type of value in Angular's DI system: + +```ts +import { InjectionToken } from '@angular/core'; + +// Create a token for a string value +export const API_URL = new InjectionToken('api.url'); + +// Create a token for a function +export const LOGGER = new InjectionToken<(msg: string) => void>('logger.function'); + +// Create a token for a complex type +export interface Config { + apiUrl: string; + timeout: number; +} +export const CONFIG_TOKEN = new InjectionToken('app.config'); +``` + +NOTE: The string parameter (e.g., `'api.url'`) is a description purely for debugging — Angular identifies tokens by their object reference, not this string. + +### InjectionToken with `providedIn: 'root'` + +An `InjectionToken` that has a `factory` results in `providedIn: 'root'` by default (but can be overidden via the `providedIn` prop). + +```ts +// 📁 /app/config.token.ts +import { InjectionToken } from '@angular/core'; + +export interface AppConfig { + apiUrl: string; + version: string; + features: Record; +} + +// Globally available configuration using providedIn +export const APP_CONFIG = new InjectionToken('app.config', { + providedIn: 'root', + factory: () => ({ + apiUrl: 'https://api.example.com', + version: '1.0.0', + features: { + darkMode: true, + analytics: false + } + }) +}); + +// No need to add to providers array - available everywhere! +@Component({ + selector: 'app-header', + template: `

Version: {{ config.version }}

` +}) +export class HeaderComponent { + config = inject(APP_CONFIG); // Automatically available +} +``` + +### When to use InjectionToken with factory functions + +InjectionToken with factory functions is ideal when you can't use a class but need to provide dependencies globally: + +```ts +// 📁 /app/logger.token.ts +import { InjectionToken, inject } from '@angular/core'; +import { APP_CONFIG } from './config.token'; + +// Logger function type +export type LoggerFn = (level: string, message: string) => void; + +// Global logger function with dependencies +export const LOGGER_FN = new InjectionToken('logger.function', { + providedIn: 'root', + factory: () => { + const config = inject(APP_CONFIG); + + return (level: string, message: string) => { + if (config.features.logging !== false) { + console[level](`[${new Date().toISOString()}] ${message}`); + } + }; + } +}); + +// 📁 /app/storage.token.ts +// Providing browser APIs as tokens +export const LOCAL_STORAGE = new InjectionToken('localStorage', { + // providedIn: 'root' is configured as the default + factory: () => window.localStorage +}); + +export const SESSION_STORAGE = new InjectionToken('sessionStorage', { + providedIn: 'root', + factory: () => window.sessionStorage +}); + +// 📁 /app/feature-flags.token.ts +// Complex configuration with runtime logic +export const FEATURE_FLAGS = new InjectionToken>('feature.flags', { + providedIn: 'root', + factory: () => { + const flags = new Map(); + + // Parse from environment or URL params + const urlParams = new URLSearchParams(window.location.search); + const enableBeta = urlParams.get('beta') === 'true'; + + flags.set('betaFeatures', enableBeta); + flags.set('darkMode', true); + flags.set('newDashboard', false); + + return flags; + } +}); +``` + +This approach offers several advantages: + +- **No manual provider configuration needed** - Works just like `providedIn: 'root'` for services +- **Tree-shakeable** - Only included if actually used +- **Type-safe** - Full TypeScript support for non-class values +- **Can inject other dependencies** - Factory functions can use `inject()` to access other services + +## Understanding manual provider configuration + +When you need more control than `providedIn: 'root'` offers, you can manually configure providers. Manual configuration through the `providers` array is useful when: + +1. **The service doesn't have `providedIn`** - Services without automatic provision must be manually provided +2. **You want a new instance** - To create a separate instance at the component/directive level instead of using the shared one +3. **You need runtime configuration** - When service behavior depends on runtime values +4. **You're providing non-class values** - Configuration objects, functions, or primitive values + +### Example: Service without `providedIn` + +```ts +import { Injectable, Component, inject } from '@angular/core'; + +// Service without providedIn +@Injectable() +export class LocalDataStore { + private data: string[] = []; + + addData(item: string) { + this.data.push(item); + } +} + +// Component must provide it +@Component({ + selector: 'app-example', + // A provider is required here because the `LocalDataStore` service has no providedIn. + providers: [LocalDataStore], + template: `...` +}) +export class ExampleComponent { + dataStore = inject(LocalDataStore); +} +``` + +### Example: Creating component-specific instances + +Services with `providedIn: 'root'` can be overridden at the component level. This ties the instance of the service to the life of a component. As a result, when the component gets destroyed, the provided service is also destroyed as well. + +```ts +import { Injectable, Component, inject } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class DataStore { + private data: ListItem[] = []; +} + +// This component gets its own instance +@Component({ + selector: 'app-isolated', + // Creates new instance of `DataStore` rather than using the root-provided instance. + providers: [DataStore], + template: `...` +}) +export class IsolatedComponent { + dataStore = inject(DataStore); // Component-specific instance +} +``` + +## Injector hierarchy in Angular + +Angular's dependency injection system is hierarchical. When a component requests a dependency, Angular starts with that component's injector and walks up the tree until it finds a provider for that dependency. Each component in your application tree can have its own injector, and these injectors form a hierarchy that mirrors your component tree. + +This hierarchy enables: + +- **Scoped instances**: Different parts of your app can have different instances of the same service +- **Override behavior**: Child components can override providers from parent components +- **Memory efficiency**: Services are only instantiated where needed + +In Angular, any element with a component or directive can provide values to all of its descendants. + +```mermaid +graph TD + subgraph platform + subgraph root + direction TB + A[SocialApp] --> B[UserProfile] + A --> C[FriendList] + C --> D[FriendEntry] + end + end +``` + +In the example above: + +1. `SocialApp` can provide values for `UserProfile` and `FriendList` +2. `FriendList` can provide values for injection to `FriendEntry`, but cannot provide values for injection in `UserProfile` because it's not part of the tree + +## Declaring a provider + +Think of Angular's dependency injection system as a hash map or dictionary. Each provider configuration object defines a key-value pair: + +- **Key (Provider identifier)**: The unique identifier you use to request a dependency +- **Value**: What Angular should return when that token is requested + +When manually providing dependencies, you typically see this shorthand syntax: + +```angular-ts +import { Component } from '@angular/core'; +import { LocalService } from './local-service'; + +@Component({ + selector: 'app-example', + providers: [LocalService] // Service without providedIn +}) +export class ExampleComponent { } +``` + +This is actually a shorthand for a more detailed provider configuration: + +```ts +{ + // This is the shorthand version + providers: [LocalService], + + // This is the full version + providers: [ + { provide: LocalService, useClass: LocalService } + ] +} +``` + +### Provider configuration object + +Every provider configuration object has two primary parts: + +1. **Provider identifier**: The unique key that Angular uses to get the dependency (set via the `provide` property) +2. **Value**: The actual dependency that you want Angular to fetch, configured with different keys based on the desired type: + - `useClass` - Provides a JavaScript class + - `useValue` - Provides a static value + - `useFactory` - Provides a factory function that returns the value + - `useExisting` - Provides an alias to an existing provider + +### Provider identifiers + +Provider identifiers allow Angular's dependency injection (DI) system to retrieve a dependency through a unique ID. You can generate provider identifiers in two ways: + +1. [Class names](#class-names) +2. [Injection tokens](#injection-tokens) + +#### Class names + +Class name use the imported class directly as the identifier: + +```angular-ts +import { Component } from '@angular/core'; +import { LocalService } from './local-service'; + +@Component({ + selector: 'app-example', + providers: [ + { provide: LocalService, useClass: LocalService } + ] +}) +export class ExampleComponent { /* ... */ } +``` + +The class serves as both the identifier and the implementation, which is why Angular provides the shorthand `providers: [LocalService]`. + +#### Injection tokens + +Angular provides a built-in [`InjectionToken`](api/core/InjectionToken) class that creates a unique object reference for injectable values or when you want to provide multiple implementations of the same interface. + +```ts +// 📁 /app/tokens.ts +import { InjectionToken } from '@angular/core'; +import { DataService } from './data-service.interface'; + +export const DATA_SERVICE_TOKEN = new InjectionToken('DataService'); +``` + +NOTE: The string `'DataService'` is a description used purely for debugging purposes. Angular identifies the token by its object reference, not this string. + +Use the token in your provider configuration: + +```angular-ts +import { Component, inject } from '@angular/core'; +import { LocalDataService } from './local-data-service'; +import { DATA_SERVICE_TOKEN } from './tokens'; + +@Component({ + selector: 'app-example', + providers: [ + { provide: DATA_SERVICE_TOKEN, useClass: LocalDataService } + ] +}) +export class ExampleComponent { + private dataService = inject(DATA_SERVICE_TOKEN); +} +``` + +#### Can TypeScript interfaces be identifiers for injection? + +TypeScript interfaces cannot be used for injection because they don't exist at runtime: + +```ts +// ❌ This won't work! +interface DataService { + getData(): string[]; +} + +// Interfaces disappear after TypeScript compilation +@Component({ + providers: [ + { provide: DataService, useClass: LocalDataService } // Error! + ] +}) +export class ExampleComponent { + private dataService = inject(DataService); // Error! +} + +// ✅ Use InjectionToken instead +export const DATA_SERVICE_TOKEN = new InjectionToken('DataService'); + +@Component({ + providers: [ + { provide: DATA_SERVICE_TOKEN, useClass: LocalDataService } + ] +}) +export class ExampleComponent { + private dataService = inject(DATA_SERVICE_TOKEN); // Works! +} +``` + +The InjectionToken provides a runtime value that Angular's DI system can use, while still maintaining type safety through TypeScript's generic type parameter. + +### Provider value types + +#### useClass + +`useClass` provides a JavaScript class as a dependency. This is the default when using the shorthand syntax: + +```ts +// Shorthand +providers: [DataService] + +// Full syntax +providers: [ + { provide: DataService, useClass: DataService } +] + +// Different implementation +providers: [ + { provide: DataService, useClass: MockDataService } +] + +// Conditional implementation +providers: [ + { + provide: StorageService, + useClass: environment.production ? CloudStorageService : LocalStorageService + } +] +``` + +#### Practical example: Logger substitution + +You can substitute implementations to extend functionality: + +```ts +import { Injectable, Component, inject } from '@angular/core'; + +// Base logger +@Injectable() +export class Logger { + log(message: string) { + console.log(message); + } +} + +// Enhanced logger with timestamp +@Injectable() +export class BetterLogger extends Logger { + override log(message: string) { + super.log(`[${new Date().toISOString()}] ${message}`); + } +} + +// Logger that includes user context +@Injectable() +export class EvenBetterLogger extends Logger { + private userService = inject(UserService); + + override log(message: string) { + const name = this.userService.user.name; + super.log(`Message to ${name}: ${message}`); + } +} + +// In your component +@Component({ + selector: 'app-example', + providers: [ + UserService, // EvenBetterLogger needs this + { provide: Logger, useClass: EvenBetterLogger } + ] +}) +export class ExampleComponent { + private logger = inject(Logger); // Gets EvenBetterLogger instance +} +``` + +#### useValue + +`useValue` provides any JavaScript data type as a static value: + +```ts +providers: [ + { provide: API_URL_TOKEN, useValue: 'https://api.example.com' }, + { provide: MAX_RETRIES_TOKEN, useValue: 3 }, + { provide: FEATURE_FLAGS_TOKEN, useValue: { darkMode: true, beta: false } } +] +``` + +IMPORTANT: TypeScript types and interfaces cannot serve as dependency values. They exist only at compile-time. + +#### Practical example: Application configuration + +A common use case for `useValue` is providing application configuration: + +```ts +// Define configuration interface +export interface AppConfig { + apiUrl: string; + appTitle: string; + features: { + darkMode: boolean; + analytics: boolean; + }; +} + +// Create injection token +export const APP_CONFIG = new InjectionToken('app.config'); + +// Define configuration +const appConfig: AppConfig = { + apiUrl: 'https://api.example.com', + appTitle: 'My Application', + features: { + darkMode: true, + analytics: false + } +}; + +// Provide in bootstrap +bootstrapApplication(AppComponent, { + providers: [ + { provide: APP_CONFIG, useValue: appConfig } + ] +}); + +// Use in component +@Component({ + selector: 'app-header', + template: `

{{ title }}

` +}) +export class HeaderComponent { + private config = inject(APP_CONFIG); + title = this.config.appTitle; +} +``` + +#### useFactory + +`useFactory` provides a function that generates a new value for injection: + +```ts +export const loggerFactory = (config: AppConfig) => { + return new LoggerService(config.logLevel, config.endpoint); +}; + +providers: [ + { + provide: LoggerService, + useFactory: loggerFactory, + deps: [APP_CONFIG] // Dependencies for the factory function + } +] +``` + +You can mark factory dependencies as optional: + +```ts +import { Optional } from '@angular/core'; + +providers: [ + { + provide: MyService, + useFactory: (required: RequiredService, optional?: OptionalService) => { + return new MyService(required, optional || new DefaultService()); + }, + deps: [RequiredService, [new Optional(), OptionalService]] + } +] +``` + +#### Practical example: Configuration-based API client + +Here's a complete example showing how to use a factory to create a service with runtime configuration: + +```ts +// Service that needs runtime configuration +class ApiClient { + constructor( + private http: HttpClient, + private baseUrl: string, + private rateLimitMs: number + ) {} + + async fetchData(endpoint: string) { + // Apply rate limiting based on user tier + await this.applyRateLimit(); + return this.http.get(`${this.baseUrl}/${endpoint}`); + } + + private async applyRateLimit() { + // Simplified example - real implementation would track request timing + return new Promise(resolve => setTimeout(resolve, this.rateLimitMs)); + } +} + +// Factory function that configures based on user tier +import { inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +const apiClientFactory = () => { + const http = inject(HttpClient); + const userService = inject(UserService); + + // Assuming userService provides these values + const baseUrl = userService.getApiBaseUrl(); + const rateLimitMs = userService.getRateLimit(); + + return new ApiClient(http, baseUrl, rateLimitMs); +}; + +// Provider configuration +export const apiClientProvider = { + provide: ApiClient, + useFactory: apiClientFactory +}; + +// Usage in component +@Component({ + selector: 'app-dashboard', + providers: [apiClientProvider] +}) +export class DashboardComponent { + private apiClient = inject(ApiClient); +} +``` + +#### useExisting + +`useExisting` creates an alias for a provider that was already defined. Both tokens return the same instance: + +```ts +providers: [ + NewLogger, // The actual service + { provide: OldLogger, useExisting: NewLogger } // The alias +] +``` + +IMPORTANT: Don't confuse `useExisting` with `useClass`. `useClass` creates separate instances, while `useExisting` ensures you get the same singleton instance. + +### Multiple providers + +Use the `multi: true` flag when multiple providers contribute values to the same token: + +```ts +export const INTERCEPTOR_TOKEN = new InjectionToken('interceptors'); + +providers: [ + { provide: INTERCEPTOR_TOKEN, useClass: AuthInterceptor, multi: true }, + { provide: INTERCEPTOR_TOKEN, useClass: LoggingInterceptor, multi: true }, + { provide: INTERCEPTOR_TOKEN, useClass: RetryInterceptor, multi: true } +] +``` + +When you inject `INTERCEPTOR_TOKEN`, you'll receive an array containing instances of all three interceptors. + +## Where can you specify providers? + +Angular offers several levels where you can register providers, each with different implications for scope, lifecycle, and performance: + +- [**Application bootstrap**](#application-bootstrap) - Global singletons available everywhere +- [**On an element (component or directive)**](#component-or-directive-providers) - Isolated instances for specific component trees +- [**Route**](#route-providers) - Feature-specific services for lazy-loaded modules + +### Application bootstrap + +Use application-level providers in `bootstrapApplication` when: + +- **The service is used across multiple feature areas** - Services like HTTP clients, logging, or authentication that many parts of your app need +- **You want a true singleton** - One instance shared by the entire application +- **The service has no component-specific configuration** - General-purpose utilities that work the same everywhere +- **You're providing global configuration** - API endpoints, feature flags, or environment settings + +```ts +// main.ts +bootstrapApplication(AppComponent, { + providers: [ + { provide: API_BASE_URL, useValue: 'https://api.example.com' }, + { provide: INTERCEPTOR_TOKEN, useClass: AuthInterceptor, multi: true }, + LoggingService, // Used throughout the app + { provide: ErrorHandler, useClass: GlobalErrorHandler } + ] +}); +``` + +**Benefits:** + +- Single instance reduces memory usage +- Available everywhere without additional setup +- Easier to manage global state + +**Drawbacks:** + +- Always included in your JavaScript bundle, even if the value is never injected +- Cannot be easily customized per feature +- Harder to test individual components in isolation + +#### Why provide during bootstrap instead of using `providedIn: 'root'`? + +You might want a provider during bootstrap when: + +- The provider has side-effects (e.g., installing the client-side router) +- The provider requires configuration (e.g., routes) +- You're using Angular's `provideSomething` pattern (e.g., `provideRouter`, `provideHttpClient`) + +### Component or directive providers + +Use component or directive providers when: + +- **The service has component-specific state** - Form validators, component-specific caches, or UI state managers +- **You need isolated instances** - Each component needs its own copy of the service +- **The service is only used by one component tree** - Specialized services that don't need global access +- **You're creating reusable components** - Components that should work independently with their own services + +```angular-ts +// Specialized form component with its own validation service +@Component({ + selector: 'app-advanced-form', + providers: [ + FormValidationService, // Each form gets its own validator + { provide: FORM_CONFIG, useValue: { strictMode: true } } + ] +}) +export class AdvancedFormComponent { } + +// Modal component with isolated state management +@Component({ + selector: 'app-modal', + providers: [ + ModalStateService // Each modal manages its own state + ] +}) +export class ModalComponent { } +``` + +**Benefits:** + +- Better encapsulation and isolation +- Easier to test components individually +- Multiple instances can coexist with different configurations + +**Drawbacks:** + +- New instance created for each component (higher memory usage) +- No shared state between components +- Must be provided wherever needed +- Always included in the same JavaScript bundle as the component or directive, even if the value is never injected + +NOTE: If multiple directives on the same element provide the same token, one will win, but which one is undefined. + +### Route providers + +Use route-level providers for: + +- **Feature-specific services** - Services only needed for particular routes or feature modules +- **Lazy-loaded module dependencies** - Services that should only load with specific features +- **Route-specific configuration** - Settings that vary by application area + +```ts +// routes.ts +export const routes: Routes = [ + { + path: 'admin', + providers: [ + AdminService, // Only loaded with admin routes + { provide: FEATURE_FLAGS, useValue: { adminMode: true } } + ], + loadChildren: () => import('./admin/admin.routes') + }, + { + path: 'shop', + providers: [ + ShoppingCartService, // Isolated shopping state + PaymentService + ], + loadChildren: () => import('./shop/shop.routes') + } +]; +``` + +## Library author patterns + +When creating Angular libraries, you often need to provide flexible configuration options for consumers while maintaining clean APIs. Angular's own libraries demonstrate powerful patterns for achieving this. + +### The `provide` pattern + +Instead of requiring users to manually configure complex providers, library authors can export functions that return provider configurations: + +```ts +// 📁 /libs/analytics/src/providers.ts +import { InjectionToken, Provider, inject } from '@angular/core'; + +// Configuration interface +export interface AnalyticsConfig { + trackingId: string; + enableDebugMode?: boolean; + anonymizeIp?: boolean; +} + +// Internal token for configuration +const ANALYTICS_CONFIG = new InjectionToken('analytics.config'); + +// Main service that uses the configuration +export class AnalyticsService { + private config = inject(ANALYTICS_CONFIG); + + track(event: string, properties?: any) { + // Implementation using config + } +} + +// Provider function for consumers +export function provideAnalytics(config: AnalyticsConfig): Provider[] { + return [ + { provide: ANALYTICS_CONFIG, useValue: config }, + AnalyticsService + ]; +} + +// Usage in consumer app +// main.ts +bootstrapApplication(AppComponent, { + providers: [ + provideAnalytics({ + trackingId: 'GA-12345', + enableDebugMode: !environment.production + }) + ] +}); +``` + +### Advanced provider patterns with options + +For more complex scenarios, you can combine multiple configuration approaches: + +```ts +// 📁 /libs/http-client/src/provider.ts +import { Provider, InjectionToken, inject } from '@angular/core'; + +// Feature flags for optional functionality +export enum HttpFeatures { + Interceptors = 'interceptors', + Caching = 'caching', + Retry = 'retry' +} + +// Configuration interfaces +export interface HttpConfig { + baseUrl?: string; + timeout?: number; + headers?: Record; +} + +export interface RetryConfig { + maxAttempts: number; + delayMs: number; +} + +// Internal tokens +const HTTP_CONFIG = new InjectionToken('http.config'); +const RETRY_CONFIG = new InjectionToken('retry.config'); +const HTTP_FEATURES = new InjectionToken>('http.features'); + +// Core service +class HttpClientService { + private config = inject(HTTP_CONFIG, { optional: true }); + private features = inject(HTTP_FEATURES); + + get(url: string) { + // Use config and check features + } +} + +// Feature services +class RetryInterceptor { + private config = inject(RETRY_CONFIG); + // Retry logic +} + +class CacheInterceptor { + // Caching logic +} + +// Main provider function +export function provideHttpClient( + config?: HttpConfig, + ...features: HttpFeature[] +): Provider[] { + const providers: Provider[] = [ + { provide: HTTP_CONFIG, useValue: config || {} }, + { provide: HTTP_FEATURES, useValue: new Set(features.map(f => f.kind)) }, + HttpClientService + ]; + + // Add feature-specific providers + features.forEach(feature => { + providers.push(...feature.providers); + }); + + return providers; +} + +// Feature configuration functions +export interface HttpFeature { + kind: HttpFeatures; + providers: Provider[]; +} + +export function withInterceptors(...interceptors: any[]): HttpFeature { + return { + kind: HttpFeatures.Interceptors, + providers: interceptors.map(interceptor => ({ + provide: INTERCEPTOR_TOKEN, + useClass: interceptor, + multi: true + })) + }; +} + +export function withCaching(): HttpFeature { + return { + kind: HttpFeatures.Caching, + providers: [CacheInterceptor] + }; +} + +export function withRetry(config: RetryConfig): HttpFeature { + return { + kind: HttpFeatures.Retry, + providers: [ + { provide: RETRY_CONFIG, useValue: config }, + RetryInterceptor + ] + }; +} + +// Consumer usage with multiple features +bootstrapApplication(AppComponent, { + providers: [ + provideHttpClient( + { baseUrl: 'https://api.example.com' }, + withInterceptors(AuthInterceptor, LoggingInterceptor), + withCaching(), + withRetry({ maxAttempts: 3, delayMs: 1000 }) + ) + ] +}); +``` + +### Why use provider functions instead of direct configuration? + +Provider functions offer several advantages for library authors: + +1. **Encapsulation** - Internal tokens and implementation details remain private +2. **Type safety** - TypeScript ensures correct configuration at compile time +3. **Flexibility** - Easily compose features with `with*` pattern +4. **Future-proofing** - Internal implementation can change without breaking consumers +5. **Consistency** - Aligns with Angular's own patterns (`provideRouter`, `provideHttpClient`, etc.) + +This pattern is extensively used in Angular's own libraries and is considered a best practice for library authors who need to provide configurable services. diff --git a/adev-es/src/content/guide/di/defining-dependency-providers.md b/adev-es/src/content/guide/di/defining-dependency-providers.md index 3702715..5663d88 100644 --- a/adev-es/src/content/guide/di/defining-dependency-providers.md +++ b/adev-es/src/content/guide/di/defining-dependency-providers.md @@ -1,30 +1,30 @@ -# Defining dependency providers +# Definiendo proveedores de dependencias -Angular provides two ways to make services available for injection: +Angular proporciona dos formas de hacer que los servicios estén disponibles para inyección: -1. **Automatic provision** - Using `providedIn` in the `@Injectable` decorator or by providing a factory in the `InjectionToken` configuration -2. **Manual provision** - Using the `providers` array in components, directives, routes, or application config +1. **Provisión automática** - Usando `providedIn` en el decorador `@Injectable` o proporcionando un factory en la configuración de `InjectionToken` +2. **Provisión manual** - Usando el array `providers` en componentes, directivas, rutas o configuración de la aplicación -In the [previous guide](/guide/di/creating-and-using-services), you learned how to create services using `providedIn: 'root'`, which handles most common use cases. This guide explores additional patterns for both automatic and manual provider configuration. +En la [guía anterior](/guide/di/creating-and-using-services), aprendiste cómo crear servicios usando `providedIn: 'root'`, lo cual maneja la mayoría de los casos de uso comunes. Esta guía explora patrones adicionales para la configuración de proveedores tanto automática como manual. -## Automatic provision for non-class dependencies +## Provisión automática para dependencias que no son clases -While the `@Injectable` decorator with `providedIn: 'root'` works great for services (classes), you might need to provide other types of values globally - like configuration objects, functions, or primitive values. Angular provides `InjectionToken` for this purpose. +Mientras que el decorador `@Injectable` con `providedIn: 'root'` funciona muy bien para servicios (clases), podrías necesitar proveer otros tipos de valores globalmente — como objetos de configuración, funciones o valores primitivos. Angular proporciona `InjectionToken` para este propósito. -### What is an InjectionToken? +### ¿Qué es un InjectionToken? -An `InjectionToken` is an object that Angular's dependency injection system uses to uniquely identify values for injection. Think of it as a special key that lets you store and retrieve any type of value in Angular's DI system: +Un `InjectionToken` es un objeto que el sistema de inyección de dependencias en Angular usa para identificar valores de forma única para la inyección. Piensa en él como una clave especial que te permite almacenar y recuperar cualquier tipo de valor en el sistema de DI en Angular: ```ts import { InjectionToken } from '@angular/core'; -// Create a token for a string value +// Crea un token para un valor de tipo string export const API_URL = new InjectionToken('api.url'); -// Create a token for a function +// Crea un token para una función export const LOGGER = new InjectionToken<(msg: string) => void>('logger.function'); -// Create a token for a complex type +// Crea un token para un tipo complejo export interface Config { apiUrl: string; timeout: number; @@ -32,11 +32,11 @@ export interface Config { export const CONFIG_TOKEN = new InjectionToken('app.config'); ``` -NOTE: The string parameter (e.g., `'api.url'`) is a description purely for debugging — Angular identifies tokens by their object reference, not this string. +NOTA: El parámetro string (por ejemplo, `'api.url'`) es una descripción puramente para depuración — Angular identifica los tokens por su referencia de objeto, no por este string. -### InjectionToken with `providedIn: 'root'` +### InjectionToken con `providedIn: 'root'` -An `InjectionToken` that has a `factory` results in `providedIn: 'root'` by default (but can be overidden via the `providedIn` prop). +Un `InjectionToken` que tiene un `factory` resulta en `providedIn: 'root'` por defecto (pero puede ser sobrescrito a través de la propiedad `providedIn`). ```ts // 📁 /app/config.token.ts @@ -48,7 +48,7 @@ export interface AppConfig { features: Record; } -// Globally available configuration using providedIn +// Configuración disponible globalmente usando providedIn export const APP_CONFIG = new InjectionToken('app.config', { providedIn: 'root', factory: () => ({ @@ -61,29 +61,29 @@ export const APP_CONFIG = new InjectionToken('app.config', { }) }); -// No need to add to providers array - available everywhere! +// No es necesario agregar al array de providers - ¡disponible en todas partes! @Component({ selector: 'app-header', template: `

Version: {{ config.version }}

` }) export class HeaderComponent { - config = inject(APP_CONFIG); // Automatically available + config = inject(APP_CONFIG); // Disponible automáticamente } ``` -### When to use InjectionToken with factory functions +### Cuándo usar InjectionToken con funciones factory -InjectionToken with factory functions is ideal when you can't use a class but need to provide dependencies globally: +`InjectionToken` con funciones factory es ideal cuando no puedes usar una clase pero necesitas proveer dependencias globalmente: ```ts // 📁 /app/logger.token.ts import { InjectionToken, inject } from '@angular/core'; import { APP_CONFIG } from './config.token'; -// Logger function type +// Tipo de función de logger export type LoggerFn = (level: string, message: string) => void; -// Global logger function with dependencies +// Función de logger global con dependencias export const LOGGER_FN = new InjectionToken('logger.function', { providedIn: 'root', factory: () => { @@ -98,9 +98,9 @@ export const LOGGER_FN = new InjectionToken('logger.function', { }); // 📁 /app/storage.token.ts -// Providing browser APIs as tokens +// Proporcionando APIs del navegador como tokens export const LOCAL_STORAGE = new InjectionToken('localStorage', { - // providedIn: 'root' is configured as the default + // providedIn: 'root' está configurado como valor predeterminado factory: () => window.localStorage }); @@ -110,13 +110,13 @@ export const SESSION_STORAGE = new InjectionToken('sessionStorage', { }); // 📁 /app/feature-flags.token.ts -// Complex configuration with runtime logic +// Configuración compleja con lógica en tiempo de ejecución export const FEATURE_FLAGS = new InjectionToken>('feature.flags', { providedIn: 'root', factory: () => { const flags = new Map(); - // Parse from environment or URL params + // Parsear desde el entorno o parámetros de URL const urlParams = new URLSearchParams(window.location.search); const enableBeta = urlParams.get('beta') === 'true'; @@ -129,28 +129,28 @@ export const FEATURE_FLAGS = new InjectionToken>('feature.f }); ``` -This approach offers several advantages: +Este enfoque ofrece varias ventajas: -- **No manual provider configuration needed** - Works just like `providedIn: 'root'` for services -- **Tree-shakeable** - Only included if actually used -- **Type-safe** - Full TypeScript support for non-class values -- **Can inject other dependencies** - Factory functions can use `inject()` to access other services +- **No se necesita configuración manual de proveedores** - Funciona igual que `providedIn: 'root'` para servicios +- **Tree-shakeable** - Solo se incluye si realmente se usa +- **Seguro en tipos** - Soporte completo de TypeScript para valores que no son clases +- **Puede inyectar otras dependencias** - Las funciones factory pueden usar `inject()` para acceder a otros servicios -## Understanding manual provider configuration +## Entendiendo la configuración manual de proveedores -When you need more control than `providedIn: 'root'` offers, you can manually configure providers. Manual configuration through the `providers` array is useful when: +Cuando necesitas más control del que ofrece `providedIn: 'root'`, puedes configurar proveedores manualmente. La configuración manual a través del array `providers` es útil cuando: -1. **The service doesn't have `providedIn`** - Services without automatic provision must be manually provided -2. **You want a new instance** - To create a separate instance at the component/directive level instead of using the shared one -3. **You need runtime configuration** - When service behavior depends on runtime values -4. **You're providing non-class values** - Configuration objects, functions, or primitive values +1. **El servicio no tiene `providedIn`** - Los servicios sin provisión automática deben ser proporcionados manualmente +2. **Quieres una nueva instancia** - Para crear una instancia separada a nivel de componente/directiva en lugar de usar la compartida +3. **Necesitas configuración en tiempo de ejecución** - Cuando el comportamiento del servicio depende de valores en tiempo de ejecución +4. **Estás proporcionando valores que no son clases** - Objetos de configuración, funciones o valores primitivos -### Example: Service without `providedIn` +### Ejemplo: Servicio sin `providedIn` ```ts import { Injectable, Component, inject } from '@angular/core'; -// Service without providedIn +// Servicio sin providedIn @Injectable() export class LocalDataStore { private data: string[] = []; @@ -160,10 +160,10 @@ export class LocalDataStore { } } -// Component must provide it +// El componente debe proporcionarlo @Component({ selector: 'app-example', - // A provider is required here because the `LocalDataStore` service has no providedIn. + // Se requiere un proveedor aquí porque el servicio `LocalDataStore` no tiene providedIn. providers: [LocalDataStore], template: `...` }) @@ -172,9 +172,9 @@ export class ExampleComponent { } ``` -### Example: Creating component-specific instances +### Ejemplo: Creando instancias específicas de componente -Services with `providedIn: 'root'` can be overridden at the component level. This ties the instance of the service to the life of a component. As a result, when the component gets destroyed, the provided service is also destroyed as well. +Los servicios con `providedIn: 'root'` pueden ser sobrescritos a nivel de componente. Esto vincula la instancia del servicio al ciclo de vida de un componente. Como resultado, cuando el componente se destruye, el servicio proporcionado también se destruye. ```ts import { Injectable, Component, inject } from '@angular/core'; @@ -184,29 +184,29 @@ export class DataStore { private data: ListItem[] = []; } -// This component gets its own instance +// Este componente obtiene su propia instancia @Component({ selector: 'app-isolated', - // Creates new instance of `DataStore` rather than using the root-provided instance. + // Crea una nueva instancia de `DataStore` en lugar de usar la instancia proporcionada en root. providers: [DataStore], template: `...` }) export class IsolatedComponent { - dataStore = inject(DataStore); // Component-specific instance + dataStore = inject(DataStore); // Instancia específica del componente } ``` -## Injector hierarchy in Angular +## Jerarquía de inyectores en Angular -Angular's dependency injection system is hierarchical. When a component requests a dependency, Angular starts with that component's injector and walks up the tree until it finds a provider for that dependency. Each component in your application tree can have its own injector, and these injectors form a hierarchy that mirrors your component tree. +El sistema de inyección de dependencias en Angular es jerárquico. Cuando un componente solicita una dependencia, Angular comienza con el inyector de ese componente y recorre el árbol hacia arriba hasta encontrar un proveedor para esa dependencia. Cada componente en el árbol de tu aplicación puede tener su propio inyector, y estos inyectores forman una jerarquía que refleja tu árbol de componentes. -This hierarchy enables: +Esta jerarquía permite: -- **Scoped instances**: Different parts of your app can have different instances of the same service -- **Override behavior**: Child components can override providers from parent components -- **Memory efficiency**: Services are only instantiated where needed +- **Instancias con alcance**: Diferentes partes de tu aplicación pueden tener diferentes instancias del mismo servicio +- **Comportamiento de sobrescritura**: Los componentes hijos pueden sobrescribir proveedores de los componentes padres +- **Eficiencia de memoria**: Los servicios solo se instancian donde se necesitan -In Angular, any element with a component or directive can provide values to all of its descendants. +En Angular, cualquier elemento con un componente o directiva puede proveer valores a todos sus descendientes. ```mermaid graph TD @@ -220,19 +220,19 @@ graph TD end ``` -In the example above: +En el ejemplo anterior: -1. `SocialApp` can provide values for `UserProfile` and `FriendList` -2. `FriendList` can provide values for injection to `FriendEntry`, but cannot provide values for injection in `UserProfile` because it's not part of the tree +1. `SocialApp` puede proveer valores para `UserProfile` y `FriendList` +2. `FriendList` puede proveer valores para inyección a `FriendEntry`, pero no puede proveer valores para inyección en `UserProfile` porque no es parte del árbol -## Declaring a provider +## Declarando un proveedor -Think of Angular's dependency injection system as a hash map or dictionary. Each provider configuration object defines a key-value pair: +Piensa en el sistema de inyección de dependencias en Angular como un hash map o diccionario. Cada objeto de configuración de proveedor define un par clave-valor: -- **Key (Provider identifier)**: The unique identifier you use to request a dependency -- **Value**: What Angular should return when that token is requested +- **Clave (Identificador de proveedor)**: El identificador único que usas para solicitar una dependencia +- **Valor**: Lo que Angular debe retornar cuando ese token es solicitado -When manually providing dependencies, you typically see this shorthand syntax: +Cuando proporcionas dependencias manualmente, típicamente ves esta sintaxis abreviada: ```angular-ts import { Component } from '@angular/core'; @@ -240,46 +240,46 @@ import { LocalService } from './local-service'; @Component({ selector: 'app-example', - providers: [LocalService] // Service without providedIn + providers: [LocalService] // Servicio sin providedIn }) export class ExampleComponent { } ``` -This is actually a shorthand for a more detailed provider configuration: +Esto es en realidad una forma abreviada de una configuración de proveedor más detallada: ```ts { - // This is the shorthand version + // Esta es la versión abreviada providers: [LocalService], - // This is the full version + // Esta es la versión completa providers: [ { provide: LocalService, useClass: LocalService } ] } ``` -### Provider configuration object +### Objeto de configuración del proveedor -Every provider configuration object has two primary parts: +Cada objeto de configuración de proveedor tiene dos partes principales: -1. **Provider identifier**: The unique key that Angular uses to get the dependency (set via the `provide` property) -2. **Value**: The actual dependency that you want Angular to fetch, configured with different keys based on the desired type: - - `useClass` - Provides a JavaScript class - - `useValue` - Provides a static value - - `useFactory` - Provides a factory function that returns the value - - `useExisting` - Provides an alias to an existing provider +1. **Identificador del proveedor**: La clave única que Angular usa para obtener la dependencia (establecida a través de la propiedad `provide`) +2. **Valor**: La dependencia real que quieres que Angular obtenga, configurada con diferentes claves según el tipo deseado: + - `useClass` - Proporciona una clase JavaScript + - `useValue` - Proporciona un valor estático + - `useFactory` - Proporciona una función factory que retorna el valor + - `useExisting` - Proporciona un alias a un proveedor existente -### Provider identifiers +### Identificadores de proveedores -Provider identifiers allow Angular's dependency injection (DI) system to retrieve a dependency through a unique ID. You can generate provider identifiers in two ways: +Los identificadores de proveedores permiten al sistema de inyección de dependencias (DI) en Angular recuperar una dependencia a través de un ID único. Puedes generar identificadores de proveedores de dos formas: -1. [Class names](#class-names) -2. [Injection tokens](#injection-tokens) +1. [Nombres de clases](#nombres-de-clases) +2. [Tokens de inyección](#tokens-de-inyección) -#### Class names +#### Nombres de clases -Class name use the imported class directly as the identifier: +Los nombres de clases usan la clase importada directamente como identificador: ```angular-ts import { Component } from '@angular/core'; @@ -294,23 +294,23 @@ import { LocalService } from './local-service'; export class ExampleComponent { /* ... */ } ``` -The class serves as both the identifier and the implementation, which is why Angular provides the shorthand `providers: [LocalService]`. +La clase sirve tanto como identificador como implementación, por lo cual Angular proporciona la forma abreviada `providers: [LocalService]`. -#### Injection tokens +#### Tokens de inyección -Angular provides a built-in [`InjectionToken`](api/core/InjectionToken) class that creates a unique object reference for injectable values or when you want to provide multiple implementations of the same interface. +Angular proporciona una clase integrada [`InjectionToken`](api/core/InjectionToken) que crea una referencia de objeto única para valores inyectables o cuando quieres proveer múltiples implementaciones de la misma interfaz. ```ts -// 📁 /app/tokens.ts +// �� /app/tokens.ts import { InjectionToken } from '@angular/core'; import { DataService } from './data-service.interface'; export const DATA_SERVICE_TOKEN = new InjectionToken('DataService'); ``` -NOTE: The string `'DataService'` is a description used purely for debugging purposes. Angular identifies the token by its object reference, not this string. +NOTA: El string `'DataService'` es una descripción usada puramente para fines de depuración. Angular identifica el token por su referencia de objeto, no por este string. -Use the token in your provider configuration: +Usa el token en la configuración de tu proveedor: ```angular-ts import { Component, inject } from '@angular/core'; @@ -328,27 +328,27 @@ export class ExampleComponent { } ``` -#### Can TypeScript interfaces be identifiers for injection? +#### ¿Pueden las interfaces de TypeScript ser identificadores para inyección? -TypeScript interfaces cannot be used for injection because they don't exist at runtime: +Las interfaces de TypeScript no pueden usarse para inyección porque no existen en tiempo de ejecución: ```ts -// ❌ This won't work! +// ❌ ¡Esto no funcionará! interface DataService { getData(): string[]; } -// Interfaces disappear after TypeScript compilation +// Las interfaces desaparecen después de la compilación de TypeScript @Component({ providers: [ - { provide: DataService, useClass: LocalDataService } // Error! + { provide: DataService, useClass: LocalDataService } // ¡Error! ] }) export class ExampleComponent { - private dataService = inject(DataService); // Error! + private dataService = inject(DataService); // ¡Error! } -// ✅ Use InjectionToken instead +// ✅ Usa InjectionToken en su lugar export const DATA_SERVICE_TOKEN = new InjectionToken('DataService'); @Component({ @@ -357,33 +357,33 @@ export const DATA_SERVICE_TOKEN = new InjectionToken('DataService') ] }) export class ExampleComponent { - private dataService = inject(DATA_SERVICE_TOKEN); // Works! + private dataService = inject(DATA_SERVICE_TOKEN); // ¡Funciona! } ``` -The InjectionToken provides a runtime value that Angular's DI system can use, while still maintaining type safety through TypeScript's generic type parameter. +El `InjectionToken` proporciona un valor en tiempo de ejecución que el sistema de DI en Angular puede usar, mientras mantiene la seguridad de tipos a través del parámetro de tipo genérico de TypeScript. -### Provider value types +### Tipos de valores de proveedores #### useClass -`useClass` provides a JavaScript class as a dependency. This is the default when using the shorthand syntax: +`useClass` proporciona una clase JavaScript como dependencia. Este es el valor predeterminado cuando se usa la sintaxis abreviada: ```ts -// Shorthand +// Abreviada providers: [DataService] -// Full syntax +// Sintaxis completa providers: [ { provide: DataService, useClass: DataService } ] -// Different implementation +// Implementación diferente providers: [ { provide: DataService, useClass: MockDataService } ] -// Conditional implementation +// Implementación condicional providers: [ { provide: StorageService, @@ -392,14 +392,14 @@ providers: [ ] ``` -#### Practical example: Logger substitution +#### Ejemplo práctico: Sustitución de Logger -You can substitute implementations to extend functionality: +Puedes sustituir implementaciones para extender funcionalidad: ```ts import { Injectable, Component, inject } from '@angular/core'; -// Base logger +// Logger base @Injectable() export class Logger { log(message: string) { @@ -407,7 +407,7 @@ export class Logger { } } -// Enhanced logger with timestamp +// Logger mejorado con marca de tiempo @Injectable() export class BetterLogger extends Logger { override log(message: string) { @@ -415,7 +415,7 @@ export class BetterLogger extends Logger { } } -// Logger that includes user context +// Logger que incluye contexto de usuario @Injectable() export class EvenBetterLogger extends Logger { private userService = inject(UserService); @@ -426,22 +426,22 @@ export class EvenBetterLogger extends Logger { } } -// In your component +// En tu componente @Component({ selector: 'app-example', providers: [ - UserService, // EvenBetterLogger needs this + UserService, // EvenBetterLogger necesita esto { provide: Logger, useClass: EvenBetterLogger } ] }) export class ExampleComponent { - private logger = inject(Logger); // Gets EvenBetterLogger instance + private logger = inject(Logger); // Obtiene una instancia de EvenBetterLogger } ``` #### useValue -`useValue` provides any JavaScript data type as a static value: +`useValue` proporciona cualquier tipo de datos JavaScript como un valor estático: ```ts providers: [ @@ -451,14 +451,14 @@ providers: [ ] ``` -IMPORTANT: TypeScript types and interfaces cannot serve as dependency values. They exist only at compile-time. +IMPORTANTE: Los tipos e interfaces de TypeScript no pueden servir como valores de dependencia. Solo existen en tiempo de compilación. -#### Practical example: Application configuration +#### Ejemplo práctico: Configuración de la aplicación -A common use case for `useValue` is providing application configuration: +Un caso de uso común para `useValue` es proveer configuración de la aplicación: ```ts -// Define configuration interface +// Definir interfaz de configuración export interface AppConfig { apiUrl: string; appTitle: string; @@ -468,10 +468,10 @@ export interface AppConfig { }; } -// Create injection token +// Crear token de inyección export const APP_CONFIG = new InjectionToken('app.config'); -// Define configuration +// Definir configuración const appConfig: AppConfig = { apiUrl: 'https://api.example.com', appTitle: 'My Application', @@ -481,14 +481,14 @@ const appConfig: AppConfig = { } }; -// Provide in bootstrap +// Proveer en bootstrap bootstrapApplication(AppComponent, { providers: [ { provide: APP_CONFIG, useValue: appConfig } ] }); -// Use in component +// Usar en componente @Component({ selector: 'app-header', template: `

{{ title }}

` @@ -501,7 +501,7 @@ export class HeaderComponent { #### useFactory -`useFactory` provides a function that generates a new value for injection: +`useFactory` proporciona una función que genera un nuevo valor para inyección: ```ts export const loggerFactory = (config: AppConfig) => { @@ -512,12 +512,12 @@ providers: [ { provide: LoggerService, useFactory: loggerFactory, - deps: [APP_CONFIG] // Dependencies for the factory function + deps: [APP_CONFIG] // Dependencias para la función factory } ] ``` -You can mark factory dependencies as optional: +Puedes marcar las dependencias del factory como opcionales: ```ts import { Optional } from '@angular/core'; @@ -533,12 +533,12 @@ providers: [ ] ``` -#### Practical example: Configuration-based API client +#### Ejemplo práctico: Cliente API basado en configuración -Here's a complete example showing how to use a factory to create a service with runtime configuration: +Aquí hay un ejemplo completo que muestra cómo usar un factory para crear un servicio con configuración en tiempo de ejecución: ```ts -// Service that needs runtime configuration +// Servicio que necesita configuración en tiempo de ejecución class ApiClient { constructor( private http: HttpClient, @@ -547,38 +547,38 @@ class ApiClient { ) {} async fetchData(endpoint: string) { - // Apply rate limiting based on user tier + // Aplicar limitación de tasa basada en el nivel del usuario await this.applyRateLimit(); return this.http.get(`${this.baseUrl}/${endpoint}`); } private async applyRateLimit() { - // Simplified example - real implementation would track request timing + // Ejemplo simplificado - la implementación real rastrearía el tiempo de las peticiones return new Promise(resolve => setTimeout(resolve, this.rateLimitMs)); } } -// Factory function that configures based on user tier +// Función factory que configura según el nivel del usuario import { inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; const apiClientFactory = () => { const http = inject(HttpClient); const userService = inject(UserService); - // Assuming userService provides these values + // Asumiendo que userService proporciona estos valores const baseUrl = userService.getApiBaseUrl(); const rateLimitMs = userService.getRateLimit(); return new ApiClient(http, baseUrl, rateLimitMs); }; -// Provider configuration +// Configuración del proveedor export const apiClientProvider = { provide: ApiClient, useFactory: apiClientFactory }; -// Usage in component +// Uso en componente @Component({ selector: 'app-dashboard', providers: [apiClientProvider] @@ -590,20 +590,20 @@ export class DashboardComponent { #### useExisting -`useExisting` creates an alias for a provider that was already defined. Both tokens return the same instance: +`useExisting` crea un alias para un proveedor que ya fue definido. Ambos tokens retornan la misma instancia: ```ts providers: [ - NewLogger, // The actual service - { provide: OldLogger, useExisting: NewLogger } // The alias + NewLogger, // El servicio real + { provide: OldLogger, useExisting: NewLogger } // El alias ] ``` -IMPORTANT: Don't confuse `useExisting` with `useClass`. `useClass` creates separate instances, while `useExisting` ensures you get the same singleton instance. +IMPORTANTE: No confundas `useExisting` con `useClass`. `useClass` crea instancias separadas, mientras que `useExisting` asegura que obtienes la misma instancia singleton. -### Multiple providers +### Múltiples proveedores -Use the `multi: true` flag when multiple providers contribute values to the same token: +Usa la bandera `multi: true` cuando múltiples proveedores contribuyen valores al mismo token: ```ts export const INTERCEPTOR_TOKEN = new InjectionToken('interceptors'); @@ -615,24 +615,24 @@ providers: [ ] ``` -When you inject `INTERCEPTOR_TOKEN`, you'll receive an array containing instances of all three interceptors. +Cuando inyectas `INTERCEPTOR_TOKEN`, recibirás un array conteniendo instancias de los tres interceptors. -## Where can you specify providers? +## ¿Dónde puedes especificar proveedores? -Angular offers several levels where you can register providers, each with different implications for scope, lifecycle, and performance: +Angular ofrece varios niveles donde puedes registrar proveedores, cada uno con diferentes implicaciones para el alcance, ciclo de vida y rendimiento: -- [**Application bootstrap**](#application-bootstrap) - Global singletons available everywhere -- [**On an element (component or directive)**](#component-or-directive-providers) - Isolated instances for specific component trees -- [**Route**](#route-providers) - Feature-specific services for lazy-loaded modules +- [**Bootstrap de la aplicación**](#bootstrap-de-la-aplicación) - Singletons globales disponibles en todas partes +- [**En un elemento (componente o directiva)**](#proveedores-de-componente-o-directiva) - Instancias aisladas para árboles de componentes específicos +- [**Ruta**](#proveedores-de-ruta) - Servicios específicos de funcionalidad para módulos con lazy loading -### Application bootstrap +### Bootstrap de la aplicación -Use application-level providers in `bootstrapApplication` when: +Usa proveedores a nivel de aplicación en `bootstrapApplication` cuando: -- **The service is used across multiple feature areas** - Services like HTTP clients, logging, or authentication that many parts of your app need -- **You want a true singleton** - One instance shared by the entire application -- **The service has no component-specific configuration** - General-purpose utilities that work the same everywhere -- **You're providing global configuration** - API endpoints, feature flags, or environment settings +- **El servicio se usa en múltiples áreas de funcionalidad** - Servicios como clientes HTTP, logging o autenticación que muchas partes de tu aplicación necesitan +- **Quieres un verdadero singleton** - Una instancia compartida por toda la aplicación +- **El servicio no tiene configuración específica de componente** - Utilidades de propósito general que funcionan igual en todas partes +- **Estás proporcionando configuración global** - Endpoints de API, feature flags o configuraciones de entorno ```ts // main.ts @@ -640,84 +640,84 @@ bootstrapApplication(AppComponent, { providers: [ { provide: API_BASE_URL, useValue: 'https://api.example.com' }, { provide: INTERCEPTOR_TOKEN, useClass: AuthInterceptor, multi: true }, - LoggingService, // Used throughout the app + LoggingService, // Usado en toda la aplicación { provide: ErrorHandler, useClass: GlobalErrorHandler } ] }); ``` -**Benefits:** +**Beneficios:** -- Single instance reduces memory usage -- Available everywhere without additional setup -- Easier to manage global state +- Una única instancia reduce el uso de memoria +- Disponible en todas partes sin configuración adicional +- Más fácil de gestionar el estado global -**Drawbacks:** +**Desventajas:** -- Always included in your JavaScript bundle, even if the value is never injected -- Cannot be easily customized per feature -- Harder to test individual components in isolation +- Siempre incluido en tu bundle de JavaScript, incluso si el valor nunca se inyecta +- No puede ser personalizado fácilmente por funcionalidad +- Más difícil de probar componentes individuales de forma aislada -#### Why provide during bootstrap instead of using `providedIn: 'root'`? +#### ¿Por qué proveer durante el bootstrap en lugar de usar `providedIn: 'root'`? -You might want a provider during bootstrap when: +Podrías querer un proveedor durante el bootstrap cuando: -- The provider has side-effects (e.g., installing the client-side router) -- The provider requires configuration (e.g., routes) -- You're using Angular's `provideSomething` pattern (e.g., `provideRouter`, `provideHttpClient`) +- El proveedor tiene efectos secundarios (por ejemplo, instalar el router del lado del cliente) +- El proveedor requiere configuración (por ejemplo, rutas) +- Estás usando el patrón `provideSomething` en Angular (por ejemplo, `provideRouter`, `provideHttpClient`) -### Component or directive providers +### Proveedores de componente o directiva -Use component or directive providers when: +Usa proveedores de componente o directiva cuando: -- **The service has component-specific state** - Form validators, component-specific caches, or UI state managers -- **You need isolated instances** - Each component needs its own copy of the service -- **The service is only used by one component tree** - Specialized services that don't need global access -- **You're creating reusable components** - Components that should work independently with their own services +- **El servicio tiene estado específico del componente** - Validadores de formularios, cachés específicas de componente o gestores de estado de UI +- **Necesitas instancias aisladas** - Cada componente necesita su propia copia del servicio +- **El servicio solo es usado por un árbol de componentes** - Servicios especializados que no necesitan acceso global +- **Estás creando componentes reutilizables** - Componentes que deben funcionar independientemente con sus propios servicios ```angular-ts -// Specialized form component with its own validation service +// Componente de formulario especializado con su propio servicio de validación @Component({ selector: 'app-advanced-form', providers: [ - FormValidationService, // Each form gets its own validator + FormValidationService, // Cada formulario obtiene su propio validador { provide: FORM_CONFIG, useValue: { strictMode: true } } ] }) export class AdvancedFormComponent { } -// Modal component with isolated state management +// Componente modal con gestión de estado aislada @Component({ selector: 'app-modal', providers: [ - ModalStateService // Each modal manages its own state + ModalStateService // Cada modal gestiona su propio estado ] }) export class ModalComponent { } ``` -**Benefits:** +**Beneficios:** -- Better encapsulation and isolation -- Easier to test components individually -- Multiple instances can coexist with different configurations +- Mejor encapsulación y aislamiento +- Más fácil de probar componentes individualmente +- Múltiples instancias pueden coexistir con diferentes configuraciones -**Drawbacks:** +**Desventajas:** -- New instance created for each component (higher memory usage) -- No shared state between components -- Must be provided wherever needed -- Always included in the same JavaScript bundle as the component or directive, even if the value is never injected +- Se crea una nueva instancia por cada componente (mayor uso de memoria) +- No hay estado compartido entre componentes +- Debe ser proporcionado donde se necesite +- Siempre incluido en el mismo bundle de JavaScript que el componente o directiva, incluso si el valor nunca se inyecta -NOTE: If multiple directives on the same element provide the same token, one will win, but which one is undefined. +NOTA: Si múltiples directivas en el mismo elemento proveen el mismo token, una ganará, pero cuál es indefinido. -### Route providers +### Proveedores de ruta -Use route-level providers for: +Usa proveedores a nivel de ruta para: -- **Feature-specific services** - Services only needed for particular routes or feature modules -- **Lazy-loaded module dependencies** - Services that should only load with specific features -- **Route-specific configuration** - Settings that vary by application area +- **Servicios específicos de funcionalidad** - Servicios solo necesarios para rutas particulares o módulos de funcionalidad +- **Dependencias de módulos con lazy loading** - Servicios que solo deben cargarse con funcionalidades específicas +- **Configuración específica de ruta** - Configuraciones que varían según el área de la aplicación ```ts // routes.ts @@ -725,7 +725,7 @@ export const routes: Routes = [ { path: 'admin', providers: [ - AdminService, // Only loaded with admin routes + AdminService, // Solo se carga con las rutas de admin { provide: FEATURE_FLAGS, useValue: { adminMode: true } } ], loadChildren: () => import('./admin/admin.routes') @@ -733,7 +733,7 @@ export const routes: Routes = [ { path: 'shop', providers: [ - ShoppingCartService, // Isolated shopping state + ShoppingCartService, // Estado de compras aislado PaymentService ], loadChildren: () => import('./shop/shop.routes') @@ -741,38 +741,38 @@ export const routes: Routes = [ ]; ``` -## Library author patterns +## Patrones para autores de bibliotecas -When creating Angular libraries, you often need to provide flexible configuration options for consumers while maintaining clean APIs. Angular's own libraries demonstrate powerful patterns for achieving this. +Cuando creas bibliotecas en Angular, a menudo necesitas proveer opciones de configuración flexibles para los consumidores mientras mantienes APIs limpias. Las propias bibliotecas en Angular demuestran patrones poderosos para lograr esto. -### The `provide` pattern +### El patrón `provide` -Instead of requiring users to manually configure complex providers, library authors can export functions that return provider configurations: +En lugar de requerir que los usuarios configuren manualmente proveedores complejos, los autores de bibliotecas pueden exportar funciones que retornan configuraciones de proveedores: ```ts // 📁 /libs/analytics/src/providers.ts import { InjectionToken, Provider, inject } from '@angular/core'; -// Configuration interface +// Interfaz de configuración export interface AnalyticsConfig { trackingId: string; enableDebugMode?: boolean; anonymizeIp?: boolean; } -// Internal token for configuration +// Token interno para configuración const ANALYTICS_CONFIG = new InjectionToken('analytics.config'); -// Main service that uses the configuration +// Servicio principal que usa la configuración export class AnalyticsService { private config = inject(ANALYTICS_CONFIG); track(event: string, properties?: any) { - // Implementation using config + // Implementación usando config } } -// Provider function for consumers +// Función de proveedor para consumidores export function provideAnalytics(config: AnalyticsConfig): Provider[] { return [ { provide: ANALYTICS_CONFIG, useValue: config }, @@ -780,7 +780,7 @@ export function provideAnalytics(config: AnalyticsConfig): Provider[] { ]; } -// Usage in consumer app +// Uso en la aplicación consumidora // main.ts bootstrapApplication(AppComponent, { providers: [ @@ -792,22 +792,22 @@ bootstrapApplication(AppComponent, { }); ``` -### Advanced provider patterns with options +### Patrones avanzados de proveedores con opciones -For more complex scenarios, you can combine multiple configuration approaches: +Para escenarios más complejos, puedes combinar múltiples enfoques de configuración: ```ts // 📁 /libs/http-client/src/provider.ts import { Provider, InjectionToken, inject } from '@angular/core'; -// Feature flags for optional functionality +// Feature flags para funcionalidad opcional export enum HttpFeatures { Interceptors = 'interceptors', Caching = 'caching', Retry = 'retry' } -// Configuration interfaces +// Interfaces de configuración export interface HttpConfig { baseUrl?: string; timeout?: number; @@ -819,32 +819,32 @@ export interface RetryConfig { delayMs: number; } -// Internal tokens +// Tokens internos const HTTP_CONFIG = new InjectionToken('http.config'); const RETRY_CONFIG = new InjectionToken('retry.config'); const HTTP_FEATURES = new InjectionToken>('http.features'); -// Core service +// Servicio principal class HttpClientService { private config = inject(HTTP_CONFIG, { optional: true }); private features = inject(HTTP_FEATURES); get(url: string) { - // Use config and check features + // Usar config y verificar features } } -// Feature services +// Servicios de funcionalidad class RetryInterceptor { private config = inject(RETRY_CONFIG); - // Retry logic + // Lógica de reintentos } class CacheInterceptor { - // Caching logic + // Lógica de caché } -// Main provider function +// Función principal de proveedor export function provideHttpClient( config?: HttpConfig, ...features: HttpFeature[] @@ -855,7 +855,7 @@ export function provideHttpClient( HttpClientService ]; - // Add feature-specific providers + // Agregar proveedores específicos de funcionalidad features.forEach(feature => { providers.push(...feature.providers); }); @@ -863,7 +863,7 @@ export function provideHttpClient( return providers; } -// Feature configuration functions +// Funciones de configuración de funcionalidades export interface HttpFeature { kind: HttpFeatures; providers: Provider[]; @@ -897,7 +897,7 @@ export function withRetry(config: RetryConfig): HttpFeature { }; } -// Consumer usage with multiple features +// Uso del consumidor con múltiples funcionalidades bootstrapApplication(AppComponent, { providers: [ provideHttpClient( @@ -910,14 +910,14 @@ bootstrapApplication(AppComponent, { }); ``` -### Why use provider functions instead of direct configuration? +### ¿Por qué usar funciones de proveedor en lugar de configuración directa? -Provider functions offer several advantages for library authors: +Las funciones de proveedor ofrecen varias ventajas para autores de bibliotecas: -1. **Encapsulation** - Internal tokens and implementation details remain private -2. **Type safety** - TypeScript ensures correct configuration at compile time -3. **Flexibility** - Easily compose features with `with*` pattern -4. **Future-proofing** - Internal implementation can change without breaking consumers -5. **Consistency** - Aligns with Angular's own patterns (`provideRouter`, `provideHttpClient`, etc.) +1. **Encapsulación** - Los tokens internos y los detalles de implementación permanecen privados +2. **Seguridad de tipos** - TypeScript asegura la configuración correcta en tiempo de compilación +3. **Flexibilidad** - Compone funcionalidades fácilmente con el patrón `with*` +4. **Preparación para el futuro** - La implementación interna puede cambiar sin afectar a los consumidores +5. **Consistencia** - Se alinea con los propios patrones en Angular (`provideRouter`, `provideHttpClient`, etc.) -This pattern is extensively used in Angular's own libraries and is considered a best practice for library authors who need to provide configurable services. +Este patrón es usado extensamente en las propias bibliotecas en Angular y es considerado una buena práctica para autores de bibliotecas que necesitan proveer servicios configurables.