Modelul Strategie cu Angular și Typescript


Aș dori să implementez tiparul Strategy în Angular/Typescript și să folosesc un serviciu într-un component; serviciul primește o interfață de strategie ca parametru al constructorului. De asemenea, constrângerea este că serviciul depinde de un alt serviciu injectat la nivel Angular.

Răsfoiesc documentația, dar nu reușesc să găsesc o modalitate de a face acest lucru. Aș prefera să nu ajung la un cod suprainginerizat, căutând o soluție simplă pentru a implementa un pattern de strategie.

Vă rugăm să vedeți mai jos în codul simulat:

export interface IStrategy {
    calculate(a,b): number;
}

export class MinusStrategy implements IStrategy {
    calculate(a,b): number {
        return a - b;
    }
}

export class PlusStrategy implements IStrategy {
    calculate(a,b): number {
        return a + b;
    }
}

@Injectable() 
export class CalculatorService {
    // I understand it is not possible to have DI of the Interface here for calcStrategy 
    constructor(private http: HttpClient; private calcStrategy: IStrategy);
    
    getByCalc() {
        this.http.get('someurl?calc=' + calcStrategy.calculate(1,1));
    }
}

@Component(// skipped config here...)
export class MyComponent implements OnInit {
    // How to inject appropriate concrete strategy (and either autowire or provide httpClient?)
    constructor(private service: new CalculatorService(httpClient, new MinusStrategy()));
    
    useInComponent() {
        this.service.getByCalc();
    }
}

Răspuns #1

Opinia mea - Nu te poți baza pe DI pentru a furniza o instanță concretă în astfel de cazuri. DI nu are cum să știe ce tip de instanță este necesar în fiecare context.

Aș sugera să folosești modelul factory aici. De exemplu -

@Injectable()
export class StrategyFactory {
  createStrategy<T extends IStrategy>(c: new () => T): T {
    return new c();
  }
} 

//then you can inject StrategyFactory in your service class, use it like -
factory.createStrategy(MinusStrategy).calculate(2, 1); 

Răspunsul #2

Trebuie să folosiți o clasă abstractă în loc de interfață pentru „IStrategy”. Pentru că Angular nu suportă o interfață ca token pentru injecție. ( https://angular.io/guide/dependency-injection-providers#non-class-dependencies ). După aceea, puteți defini în providers ai modulului astfel

{ provide: IStrategy, useClass: MinusStrategy }

După aceea, CalculatorService va folosi MinusStrategy pentru a se injecta în orice componentă din acel modul care a injectat serviciul.

export abstract class IStrategy {
    abstract calculate(a,b): number;
}

export class PlusStrategy extends IStrategy {
    calculate(a,b): number {
        return a + b;
    }
}

export class MinusStrategy extends IStrategy {

    calculate(a,b): number {
        return a - b;
    }
}

@Injectable({
  providedIn: 'root',
})
export class CalculatorService {

  constructor(
    private http: HttpClient, 
    private calcStrategy: IStrategy) {};
    
    getByCalc() {
        console.log(`Result is: ${this.calcStrategy.calculate(1,1)}`);
    }
}

//The module need to add token to providers for Strategy classes.
@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [
    { provide: IStrategy, useClass: MinusStrategy }
  ],
  bootstrap: [...]
})
export class AppModule { }

Rețineți că păstrez numele „IStrategy” pentru clasa abstractă, de exemplu. Ar trebui să fie „BaseStrategy” sau altceva.

============================================================

[ 07/12/2020 17:00:00 GMT+7 ]

Am creat un proiect demo și mi-am actualizat abordarea urmând această problemă. Ai putea să îl deschizi și să vezi din nou abordarea mea.

Link Stackblitz: https://stackblitz.com/github/sangnt-developer/demo-injection-in-component-level

Link Github: https://github.com/sangnt-developer/demo-injection-in-component-level