Наследование компонентов в Angular: простой способ решить проблему с Dependency Injection

    Итак, собственно проблема: порой у нас в проекте есть много похожих компонентов, с одинаковой логикой, одинаковыми DI, свойствами итд и возникает мысль: а почему бы не вынести все это дело в базовый компонент (точнее директиву) абстрактным классом, а остальные компоненты уже наследовать? На самом деле мысль хорошая, принцип DRY соблюдается, имеем один источник истины, при изменении той самой общей логики не придется прыгать по всем компонентам итд.

    Но есть один нюанс: эти злосчастные конструкторы. Нужно в каждом наследуемом компоненте передавать все DI в конструктор родителя.

    constructor ( 
    	customService: CustomService, 
    	additionalService: AdditionalService
    ) {
    		super(customService, additionalService)
    }

    выглядит не очень, но это полбеды. Беда в том, что если у нас в базовом классе добавляется DI, нам придется прыгать по всем компонентам-наследникам и добавлять эту зависимость в конструктор. Плакал наш DRY :-))

    Попробуем сделать по другому: вместе с базовым компонентом создадим Injectable-класс, куда завернем все зависимости. И заинжектим его в базовый класс

    @Injectable()
    export class MyBaseComponentDependences {
    		constructor(
        	public customService: CustomService,
          public additionalService: AdditionalService
          ) {}    
    }
    @Directive()
    export abstract class MyBaseComponent<T> {
    
    		//Пример использования сервиса в родительском классе
    		shareEntity = this.deps.additionalService.getShare()
        
    		protected constructor(
        	public deps: MyBaseComponentDependences
         ) {}
    }

    Класс-наследник будет выглядеть так

    @Component({providers: [MyBaseComponentDependences] })
    export class MyChildComponent extends MyBaseComponent<MyDto> {
    		
        //Пример использования сервиса в классе-наследнике
        customEntity = this.deps.customService.getEntity()
        
        constructor(deps: MyBaseComponentDependences) {
        	super(deps);
         }
    }

    Теперь, если у нас в базовый класс добавляется DI мы меняем только класс MyBaseComponentDependences, все остальное остается как есть. Проблема решена

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

    Комментарии 7

      0
      К angulardart это применимо?
        –1

        А не проще передать injector в конструктор?

          +1

          Я вам сейчас покажу секретную технику, только вы её никому не показывайте, а то уволят из Ангуляра...


          @Directive()
          export abstract class MyBaseComponent<T> {
          
               //Пример использования сервиса в родительском классе
               shareEntity = this.deps.get(AdditionalService).getShare()
          
               protected constructor(
                  public deps: Injector
               ) {}
          
          }
          
          @Component()
          export class MyChildComponent extends MyBaseComponent {
          
              //Пример использования сервиса в классе-наследнике
              customEntity = this.deps.get(CustomService).getEntity()
          
          }

          Пишу по памяти, Ангуляр уже года 3 не трогал.

            +3
            Много лет программирую на angular. Возникали также мысли наследования, но в итоге пришел к выводу, что бесполезное это дело. Костылей только больше будет. Лучше применять композицию, чем наследование
              +2

              Я работаю с джавой, ангуляр трогаю изредка. Тем не менее настоятельно рекомендую не делать так никогда.


              Если нужна логика из CustomerService — нужно инджектить CustomerService. Если нужна логика из AdditionalService, нужно инджектить AdditionalService. Если нужна логика из обоих, то нужно инджектить оба.


              Приём, описываемый в статье имеет, смысл только если все классы из проекта используют методы и из AdditionalService и из CustomerService. Даже в этой подозрительной ситуации я бы инджектил сервисы отдельно. В крайнем случае выделил бы обёртку для используемых во всех классах методов. Но мне представить такую ситуацию очень непросто.


              Лично я выступаю за то, чтобы просто вообще запретить наследование в компонентах, чтобы ни у кого не возникло соблазна сделать что-то похожее на описанное в статье. В таких случая нужно использовать композицию. Как и 90% остальных случаев.

                0

                к тому же для расширения логики компонента есть еще директивы

                0
                Лично я выступаю за то, чтобы просто вообще запретить наследование в компонентах, чтобы ни у кого не возникло соблазна сделать что-то похожее на описанное в статье. В таких случая нужно использовать композицию. Как и 90% остальных случаев.

                Разделяю такой подход, target style наше всё. parent нужен для проброса, в том случае если parent[name] это контейнер.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое