Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Feature Request: Support defining injectables on Interface Collapse Pattern #62712

Closed
@ahardin1

Description

@ahardin1

Which @angular/* package(s) are relevant/related to the feature request?

core

Description

The interface collapse pattern (using a const with constructor interface) cannot use Angular decorators like@Injectable().

Key Use Case - Server-Side Rendering

interfaceCartService{events:Observable<CartEvent>;add(productId:string):Observable<AddProductResponse>;remove(productId:string):Observable<RemoveProductResponse>;}interfaceCartServiceConstructor{new():CartService;prototype:CartService;}@Injectable({providedIn:"root"})// TS errorconstCartService:CartServiceConstructor=class{     #http=inject(HttpClient);     #events=newSubject<CartEvent>();events=this.#events.asObservable();add(productId:string):Observable<AddProductResponse>{// Implementation}remove(productId:string):Observable<RemoveProductResponse>{// Implementation}}// Clean noop implementation for SSRexportconstnoopCartService:CartService={events:NEVER,add(){returnNEVER;},remove(){returnNEVER;}};// In server moduleproviders:[{provide:CartService,useValue:noopCartService}]

Current Problem

With traditional class declarations, TypeScript requires noop implementations to include private members:

@Injectable({providedIn:'root'})classCartService{#http=inject(HttpClient);#events=newSubject<CartEvent>();events=this.#events.asObservable();add(productId:string):Observable<AddProductResponse>{returnthis.#http.post// Implementation().pipe(tap(()=>this.#events.next(newProductAddedEvent())));}remove(productId:string):Observable<RemoveProductResponse>{returnthis.#http.post// Implementation().pipe(tap(()=>this.#events.next(newProductRemovedEvent())));}};// ❌ TypeScript error - must implement private #http, #events@Injectable({providedIn:'root'})classNoopCartServiceimplementsCartService{events=NEVER;add(){returnNEVER;}remove(){returnNEVER;}}

And abstract classes lead to situations where devs can easily make a mistake (especially junior engineers)

abstractclassAbstractCartService{#http=inject(HttpClient);#events=newSubject<CartEvent>();events=this.#events.asObservable();abstractadd(productId:string):Observable<AddProductResponse>abstractremove(productId:string):Observable<RemoveProductResponse>;};@Injectable({providedIn:'root'})classCartServiceextendsAbstractCartService{#http=inject(HttpClient);#events=newSubject<CartEvent>();events=this.#events.asObservable();add(productId:string):Observable<AddProductResponse>{// Implementation}remove(productId:string):Observable<RemoveProductResponse>{// Implementation}// This should have been added to the abstract class.removeAll():Observable<RemoveAllProductsResponse>{// Implementation}};exportconstnoopCartService:CartService={events:NEVER,add(){returnNEVER}remove(){returnNEVER}// BUG - the noop variation did not require the removeAll method when it actually needs it.};

Proposed solution

Some public variation of thedefineInjectable

constCartService:CartServiceConstructor=class{// Obviously wouldn't use the internal symbolsstaticɵprov=/**@pureOrBreakMyCode *//*@__PURE__ */ɵɵdefineInjectable({token:CartService,providedIn:'root',factory:()=>newCartService(),});};

Alternatives considered

  1. UsingInjectionToken's. I have found in our org (which maintains 7ish angular applications) junior Angular engineers expect a service to be a class so this one tends to confuse them. Not a big deal to workaround though
constCART_SERVICE=newInjectionToken('CART_SERVICE',{factory:()=>newCartService(),});
  1. Defining aCartManager seperately from theCartService (my current favorite workaround). This still requires some social enforcement but it's better than the abstract class.
interfaceCartManager{events:Observable<CartEvent>;add(productId:string):Observable<AddProductResponse>;remove(productId:string):Observable<RemoveProductResponse>;}interfaceCartManagerConstructor{new():CartManager;prototype:CartManager;}constCartManager:CartManagerConstructor=class{#http=inject(HttpClient);#events=newSubject<CartEvent>();events=this.#events.asObservable();add(productId:string):Observable<AddProductResponse>{// Implementation}remove(productId:string):Observable<RemoveProductResponse>{// Implementation}};@Injectable({providedIn:'root'})// This service purely exists to apply the@Injectable decorator.exportclassCartServiceextendsCartManager{// Do NOT add any logic here. Add it in the CartManager instead.}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp