@@ -791,9 +791,7 @@ class TcbDirectiveInputsOp extends TcbOp {
791791 dirId , ts . factory . createIdentifier ( fieldName ) ) ;
792792 }
793793
794- if ( isTwoWayBinding ) {
795- target = this . getTwoWayBindingExpression ( target ) ;
796- } else if ( isSignal ) {
794+ if ( isSignal ) {
797795 // For signal inputs, we unwrap the target `InputSignal`. Note that
798796 // we intentionally do the following things:
799797 // 1. keep the direct access to `dir.[field]` so that modifiers are honored.
@@ -811,6 +809,10 @@ class TcbDirectiveInputsOp extends TcbOp {
811809 target = ts . factory . createElementAccessExpression ( target , inputSignalBrandWriteSymbol ) ;
812810 }
813811
812+ if ( isTwoWayBinding ) {
813+ target = this . getTwoWayBindingTarget ( target ) ;
814+ }
815+
814816 if ( attr . attribute . keySpan !== undefined ) {
815817 addParseSpanInfo ( target , attr . attribute . keySpan ) ;
816818 }
@@ -849,50 +851,33 @@ class TcbDirectiveInputsOp extends TcbOp {
849851 }
850852 }
851853
852- private getTwoWayBindingExpression ( target : ts . LeftHandSideExpression ) : ts . LeftHandSideExpression {
853- // TODO(crisbeto): we should be able to avoid the extra variable that captures the type.
854- // Skipping it for since we don't have a good way to convert the `PropertyAccessExpression`
855- // into an `QualifiedName`.
854+ private getTwoWayBindingTarget ( target : ts . LeftHandSideExpression ) : ts . LeftHandSideExpression {
856855 // Two-way bindings to inputs allow both the input's defined type and a `WritableSignal`
857856 // of that type. For example `[(value)]="val"` where `@Input() value: number | string`
858857 // allows `val` to be `number | string | WritableSignal<number | string>`. We generate the
859858 // following expressions to expand the type:
860859 // ```
861860 // var captureType = dir.value;
862- // (id as unknown as ɵConditionallyUnwrapSignal<typeof captureType> |
863- // WritableSignal<ɵConditionallyUnwrapSignal<typeof captureType>>) = expression;
861+ // (dir.value as typeof captureType | WritableSignal<typeof captureType>) = expression;
864862 // ```
865- // Note that the TCB can be simplified a bit by making the union type part of the utility type
866- // (e.g. `type ɵTwoWayAssign<T> = T extends Signal ? ReturnType<T> |
867- // WritableSignal<ReturnType<T>> : ReturnType<T> | WritableSignal<ReturnType<T>>`), however at
868- // the time of writing, this generates a suboptimal diagnostic message where TS splits up the
869- // signature, e.g. "Type 'number' is not assignable to type 'string | boolean |
870- // WritableSignal<string> | WritableSignal<false> | WritableSignal<true>'" instead of Type
871- // 'number' is not assignable to type 'string | boolean | WritableSignal<string | boolean>'.
863+ // Some notes:
864+ // - We wrap the assignment, insted of for example declaring a variable and assigning to it,
865+ // because keeping the assignment makes it easier to do lookups in the language service.
866+ // - The `captureType` variable can be inline, but for signal input expressions it can be
867+ // long so we use it make the code a bit neater. It also saves us some code that would
868+ // have to convert property/element access expressions into type query nodes.
872869 const captureType = this . tcb . allocateId ( ) ;
870+ const captureTypeVar = tsCreateVariable ( captureType , target ) ;
871+ markIgnoreDiagnostics ( captureTypeVar ) ;
872+ this . scope . addStatement ( captureTypeVar ) ;
873873
874- // ɵConditionallyUnwrapSignal<typeof captureType>
875- const unwrappedRef = this . tcb . env . referenceExternalType (
876- R3Identifiers . ConditionallyUnwrapSignal . moduleName ,
877- R3Identifiers . ConditionallyUnwrapSignal . name ,
878- [ new ExpressionType ( new TypeofExpr ( new WrappedNodeExpr ( captureType ) ) ) ] ) ;
879-
880- // WritableSignal<ɵConditionallyUnwrapSignal<typeof captureType>>
874+ const typeQuery = ts . factory . createTypeQueryNode ( captureType ) ;
881875 const writableSignalRef = this . tcb . env . referenceExternalType (
882876 R3Identifiers . WritableSignal . moduleName , R3Identifiers . WritableSignal . name ,
883- [ new ExpressionType ( new WrappedNodeExpr ( unwrappedRef ) ) ] ) ;
884-
885- // ɵConditionallyUnwrapSignal<typeof captureType> |
886- // WritableSignal<ɵConditionallyUnwrapSignal<typeof captureType>>
887- const type = ts . factory . createUnionTypeNode ( [ unwrappedRef , writableSignalRef ] ) ;
888- this . scope . addStatement ( tsCreateVariable ( captureType , target ) ) ;
877+ [ new ExpressionType ( new TypeofExpr ( new WrappedNodeExpr ( captureType ) ) ) ] ) ;
889878
890- // (target as unknown as ɵConditionallyUnwrapSignal<typeof captureType> |
891- // WritableSignal<ɵConditionallyUnwrapSignal<typeof captureType>>)
892879 return ts . factory . createParenthesizedExpression ( ts . factory . createAsExpression (
893- ts . factory . createAsExpression (
894- target , ts . factory . createKeywordTypeNode ( ts . SyntaxKind . UnknownKeyword ) ) ,
895- type ) ) ;
880+ target , ts . factory . createUnionTypeNode ( [ typeQuery , writableSignalRef ] ) ) ) ;
896881 }
897882}
898883
0 commit comments