Angular doesn't have a "global variable" in the traditional sense, and reaching for one on the window object defeats the framework's own dependency injection system — a shared, injectable configuration service is the idiomatic way to make a value accessible application-wide, and it pairs naturally with the pattern for building genuinely reusable components.
A shared configuration service, instead of a global variable
@Injectable({ providedIn: 'root' })
export class AppConfigService {
readonly apiUrl = 'https://api.example.com';
readonly appName = 'My App';
readonly maxUploadSizeMb = 10;
}
constructor(private config: AppConfigService) {}
ngOnInit() {
console.log(this.config.apiUrl);
}
This achieves the same practical goal as a global variable — one shared value accessible anywhere — but through Angular's own dependency injection, which keeps it mockable in tests and visible as an explicit dependency in any component's constructor, unlike a value silently read off the global window object.
Environment-specific configuration
// environment.ts
export const environment = {
production: false,
apiUrl: 'http://localhost:8000/api',
};
// environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.example.com',
};
import { environment } from '../environments/environment';
console.log(environment.apiUrl);
Angular's build system automatically swaps in the correct environment file based on the build configuration (ng build --configuration production) — this is the standard place for genuinely environment-dependent values like API URLs, distinct from the AppConfigService pattern above, which suits values that don't change between environments.
Building a reusable component with @Input
@Component({
selector: 'app-badge',
template: `{{ text }}`
})
export class BadgeComponent {
@Input() text: string = '';
@Input() variant: 'success' | 'warning' | 'danger' = 'success';
}
@Input() properties are what make a component configurable from the outside — without them, a component's content and appearance would be hardcoded, defeating the point of reusing it in different contexts.
Emitting events from a reusable component with @Output
@Component({
selector: 'app-confirm-button',
template: ``
})
export class ConfirmButtonComponent {
@Input() label: string = 'Confirm';
@Output() confirmed = new EventEmitter();
onConfirm() {
this.confirmed.emit();
}
}
@Output() with EventEmitter is how a reusable component communicates back up to whatever parent is using it — the parent decides what actually happens on confirmation (deleteItem() here), while the reusable component itself stays generic and unaware of that specific parent's logic.
Why this input/output pattern is the core of Angular component reusability
Together, @Input and @Output form a clear, explicit contract for a component — what it needs from its parent, and what it reports back — which is what actually makes a component genuinely reusable across many different parts of an application, rather than being written for one specific use case and copy-pasted with edits for the next.