Как предоставить сервис, использующий дженерики в Angular4?

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

@Injectable()
export class DetailResolver<T, R extends Repository<T>> implements Resolve<T> {

  constructor(private repositoryService: R, private router: Router) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> {
    // use repositoryService to resolve this route
  }
}

Можно указать тип объекта T и тип репозитория R. Однако у меня проблемы с использованием этого сервиса:

const appRoutes: Routes = [
  // other routes
  {
    path: ':id',
    component: MessageEditComponent,
    resolve: {
      message: DetailResolver<Message, MessageService>
    }
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(appRoutes)
  ],
  exports: [
    RouterModule
  ],
  providers: [
    DetailResolver<Message, MessageService>
  ]
})

Как только я укажу общий тип в DetailResolver<Message, MessageService>, компилятор хочет, чтобы я создал экземпляр:

Значение типа typeof DetailResolver не может быть вызвано. Ты хотел добавить новые?

Я не совсем знаком с внутренностями angular4 DI. Может кто-нибудь описать, что не так, есть ли решение этой проблемы? Я использую угловой 4.3.


person Upvote    schedule 12.10.2017    source источник


Ответы (1)


Когда вы регистрируете провайдера с помощью инжектора, система DI использует этот токен — или то, что иначе можно было бы рассматривать как ключ — для поддержки карты токен-провайдер, на которую ссылаются каждый раз, когда запрашивается зависимость. за.

Токены, как правило, представляют собой объекты, которые являются уникальными и символическими, поэтому концепция дженериков прямо противоречит этой области именно потому, что токен будет функционировать как ключ. Обобщения — это артефакт времени разработки, то есть еще один способ сказать, что они исчезнут из сгенерированного JavaScript и, следовательно, не оставят информацию о своем типе для поиска Angular во время выполнения. Интерфейсы TypeScript также не являются допустимыми токенами по той же причине.

Абстрактные классы

Мой первый вариант — использовать abstractclasses. Абстрактные классы решают здесь две проблемы: 1) Хотя они не могут быть созданы напрямую, в отличие от интерфейса, они могут содержать детали реализации и, следовательно, могут быть скомпилированы в Javascript. 2) Вы получаете токен DI через архитектуру, основанную на расширении, которая может очень хорошо работать для вашей архитектуры.

В вашем случае вы можете сделать что-то вроде следующего:

@Injectable()
export abstract class DetailResolver<T, R extends Repository<T>> implements Resolve<T> {...}

@Injectable()
export class MessageResolver extends DetailResolver<Message, MessageService> {...}

А затем в NgModule вы должны предоставить его следующим образом:

providers: [{ provide: DetailResolver, useClass: MessageResolver }]

Токен инъекции

Другим вариантом является использование InjectionToken (известного как OpaqueTokens до Angular 4.0). InjectionTokens — это объекты, которые используются исключительно как токены DI; однако, в отличие от их предшественника, OpaqueToken, они должны быть типизированы в зависимости от типа значения, которое они будут вводить.

const MESSAGE_RESOLVER = new InjectionToken<DetailResolver<Message, MessageService>>('MESSAGE_RESOLVER');

Вы заметите, что вы можете указать тип при создании экземпляра InjectionToken, чтобы компилятор поддерживал вас на этом пути. Это применяется, когда вы добавляете провайдера в свой NgModule.

providers: [{ provide: MESSAGE_RESOLVER, useClass: MessageResolver }]

Стоит отметить, что useClass там технически не требуется; любые другие альтернативы, такие как useValue или useFactory, допустимы здесь при предоставлении соответствующего.

Надеюсь, что это полезно и дает некоторые разъяснения по этому вопросу!

person emarticor    schedule 12.10.2017