Шаблон стратегии с Angular и машинописным текстом - PullRequest
1 голос
/ 11 июля 2020

Я хотел бы реализовать шаблон стратегии в Angular / Typescript и использовать службу в компоненте; service принимает интерфейс стратегии в качестве параметра конструктора. Кроме того, ограничение заключается в том, что служба зависит от какой-либо другой внедренной службы на уровне angular.

Я копаюсь в документации, но не могу найти способ сделать это. Я не хотел бы заканчиваться чрезмерно продуманным кодом в поисках простого решения для реализации шаблона стратегии.

См. Макет кода ниже:

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

Ответы [ 4 ]

1 голос
/ 11 июля 2020

Вам нужно использовать абстрактный класс вместо интерфейса для "IStrategy". Потому что Angular не поддерживает интерфейс как токен для инъекции. (https://angular.io/guide/dependency-injection-providers#non -класс-зависимости ). После этого вы можете указать в поставщиках модуля, как показано ниже

{ provide: IStrategy, useClass: MinusStrategy }

После этого CalculatorService будет использовать MinusStrategy для внедрения в любой компонент в этом модуле, который внедрил службу.

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

Обратите внимание, что, например, я оставил имя IStrategy для абстрактного класса. Это должно быть "BaseStrategy" или что-то еще.

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

[12.07.2020 17:00:00 GMT + 7]

Я создал демонстрационный проект и обновил свой подход, следуя этой проблеме. Не могли бы вы открыть его и снова увидеть мой подход.

Ссылка на Stackblitz: https://stackblitz.com/github/sangnt-developer/demo-injection-in-component-level

Ссылка на Github: https://github.com/sangnt-developer/demo-injection-in-component-level

1 голос
/ 11 июля 2020

Мои два цента - в таком случае вы не можете полагаться на DI в предоставлении конкретного экземпляра. У DI нет способа узнать, какой тип экземпляра требуется в каждом контексте.

Я бы предложил здесь использовать фабричный шаблон. Например -

@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); 
1 голос
/ 11 июля 2020

Один из способов сделать это - определить собственный токен внедрения и использовать этот токен в объявлении поставщика компонентов (см. https://angular.io/guide/dependency-injection-in-action#supply -a-custom-provider-with-inject для получения дополнительной информации) :

export const CalculatorStrategy = new InjectionToken<string>('CalculatorStrategy');

@Component({
    providers: [
        // define the actual strategy-implementation here, setting it as `useClass`-provider
        {provide: CalculatorStrategy, useClass: PlusStrategy}
    ]
})
export class MyComponent implements OnInit {
    
    constructor(private service: CalculatorService) {
    }

    useInComponent() {
        this.service.getByCalc();
    }
}

@Injectable()
export class CalculatorService {

    constructor(private http: HttpClient, @Inject(CalculatorStrategy) private calcStrategy: IStrategy);

    getByCalc() {
        this.http.get('someurl?calc=' + this.calcStrategy.calculate(1, 1));
    }
}
0 голосов
/ 17 августа 2020

Я думаю, что это может быть достигнуто на уровне компонентов, добавив службу и токен в раздел поставщиков.

 @Component({
    ...
    ...
    proverders: [
    CalculatorService,
    {provide: IStrategy, useClass: 
    PlusStrategy
    ]
    })

Затем вставьте службу в конструктор

...