AngularDependency InjectionArchitectureDesign PatternsClean Code

Angular DI: Advanced Patterns for Seniors

A
Angular Architect
Featured Guide 18 min read

Stop injecting classes everywhere.

Most developers only use DI for singleton services.

But Angular's DI system is a hierarchical, key-value store that can hold configurations, functions, and even lists of plugins.

Deep Dive: Resolution Modifiers

Control where Angular looks for dependencies:

  • @Optional(): Don't crash if not found (returns null).
  • @Self(): Look only in this component's injector.
  • @SkipSelf(): Start looking in the parent injector.
  • @Host(): Stop looking at the host component boundary.

02. Config Objects

Don't hardcode API URLs. Use an InjectionToken to inject environment variables into your library.

// config.token.ts
export const
API_URL =
new
InjectionToken<string>('API_URL');

// Configure in main.ts
providers: [ {'{'} provide: API_URL, useValue: 'https://api.myapp.com' {'}'} ]

03. Multi Providers

Want to build a pluggable validation system? Use multi: true. This allows you to provide multiple classes for the same token.

constructor(@Inject(VALIDATORS) private validators: Validator[])

04. The Senior Engineer's Take

Tree Shaking

Use providedIn: 'root' whenever possible.

If you register providers in an @NgModule array, they cannot be tree-shaken if unused. 'root' providers are lazily instantiated and removed if not referenced.

Interactive Playground

import React, { useState } from 'react';

// 🏗️ DI System Visualizer

export default function DiDemo() {
    const [plugins, setPlugins] = useState([
        { id: 1, name: 'LoggerPlugin', active: true },
        { id: 2, name: 'AuthPlugin', active: true },
        { id: 3, name: 'CachePlugin', active: false },
    ]);
    const [logs, setLogs] = useState([]);

    const togglePlugin = (id) => {
        setPlugins(prev => prev.map(p => p.id === id ? { ...p, active: !p.active } : p));
        setLogs(prev => [...prev, `DI Container: Re-calculating providers...`]);
    };

    const runUse = async () => {
        setLogs([]);
        const activePlugins = plugins.filter(p => p.active);
        
        setLogs(prev => [...prev, `System: Requesting InjectionToken<PLUGIN_List>`]);
        await wait(500);
        
        if (activePlugins.length === 0) {
            setLogs(prev => [...prev, `DI: Found 0 providers. Returning empty array.`]);
        } else {
            setLogs(prev => [...prev, `DI: Found ${activePlugins.length} providers (multi: true).`]);
            for (const p of activePlugins) {
                await wait(400);
                setLogs(prev => [...prev, `DI: Instantiating ${p.name}...`]);
            }
            await wait(400);
            setLogs(prev => [...prev, `System: Ready to use plugins.`]);
        }
    };

    const wait = (ms) => new Promise(r => setTimeout(r, ms));

    return (
        <div className="bg-slate-50 dark:bg-slate-950 p-8 rounded-3xl border border-slate-200 dark:border-slate-800 shadow-xl">
             <div className="flex justify-between items-center mb-10">
                <h3 className="text-2xl font-black text-gray-900 dark:text-white flex items-center gap-3">
                    <span className="text-red-500">🏗️</span> Dependency Injection
                </h3>
            </div>

            <div className="flex flex-col md:flex-row gap-8">
                
                {/* Configuration */}
                <div className="w-full md:w-1/3 bg-white dark:bg-slate-900 p-6 rounded-2xl border border-slate-200 dark:border-slate-800">
                    <div className="font-bold text-gray-500 uppercase text-xs mb-4 flex items-center gap-2">
                        <span>⚙️</span> App Configuration (providers)
                    </div>
                    
                    <div className="space-y-2">
                        {plugins.map(p => (
                            <div key={p.id} className="flex items-center justify-between p-3 bg-gray-50 dark:bg-slate-800 rounded-lg">
                                <span className={p.active ? "font-bold text-gray-800 dark:text-white" : "text-gray-400 line-through"}>
                                    {p.name}
                                </span>
                                <button 
                                    onClick={() => togglePlugin(p.id)}
                                    className={`w-10 h-6 rounded-full p-1 transition-colors ${p.active ? 'bg-green-500' : 'bg-gray-300 dark:bg-slate-600'}`}
                                >
                                    <div className={`w-4 h-4 bg-white rounded-full transition-transform ${p.active ? 'translate-x-4' : 'translate-x-0'}`}></div>
                                </button>
                            </div>
                        ))}
                    </div>
                     
                    <div className="mt-6 text-xs text-gray-400 bg-gray-100 dark:bg-black p-4 rounded font-mono">
                        providers: [<br/>
                        {plugins.map(p => (
                            <div key={p.id} className={p.active ? 'text-green-600 dark:text-green-400' : 'opacity-20'}>
                                &nbsp;&nbsp;{'{'} provide: PLUGINS, useClass: {p.name}, multi: true {'}'},
                            </div>
                        ))}
                        ]
                    </div>
                </div>

                {/* Execution */}
                <div className="flex-1 flex flex-col gap-4">
                     <button 
                        onClick={runUse}
                        className="p-4 bg-red-600 text-white rounded-xl font-bold hover:bg-red-700 transition flex items-center justify-center gap-2"
                    >
                        <span></span> Run Application
                    </button>

                    <div className="flex-1 bg-black rounded-xl p-6 font-mono text-xs overflow-y-auto h-[300px]">
                        <div className="text-gray-500 border-b border-gray-800 pb-2 mb-4 uppercase font-bold">Injector Logs</div>
                        <div className="space-y-2">
                            {logs.map((log, i) => (
                                <div key={i} className="animate-in slide-in-from-left-2 text-green-400">
                                    &gt; {log}
                                </div>
                            ))}
                            {logs.length === 0 && <span className="text-gray-600">Waiting for bootstrap...</span>}
                        </div>
                    </div>
                </div>

            </div>
        </div>
    );
}