DEV Community

loading...

Functional programming with Angular. 2. Typechecking

Lonli-Lokli
・3 min read

Typechecking is what typescript does for us. Unfortunately, it's not always true in core part of Angular world - templates.

In previous part we completed initial approach with Either type, representing union of Data (Right) and Error (Left). Now we will look in more details how this type is handled in @sweet-monads/either.

Either#mapRight

function mapRight<L, R, NewR>(fn: (val: R) => NewR): Either<L, NewR>;
Enter fullscreen mode Exit fullscreen mode

Returns mapped by fn function value wrapped by Either if Either is Right otherwise Left with L value

Example:

const v1 = right<Error, number>(2);
const v2 = left<Error, number>(new Error());

const newVal1 = v1.map(a => a.toString()); // Either<Error, string>.Right with value "2"
const newVal2 = v2.map(a => a.toString()); // Either<Error, string>.Left with value new Error()
Enter fullscreen mode Exit fullscreen mode

Either#mapLeft

function mapLeft<L, R, NewL>(fn: (val: L) => NewL): Either<NewL, R>;
Enter fullscreen mode Exit fullscreen mode

Returns mapped by fn function value wrapped by Either if Either is Left otherwise Right with R value

Example:

const v1 = right<Error, number>(2);
const v2 = left<Error, number>(new Error());

const newVal1 = v1.mapLeft(a => a.toString()); // Either<string, number>.Right with value 2
const newVal2 = v2.mapLeft(a => a.toString()); // Either<string, number>.Left with value "Error"
Enter fullscreen mode Exit fullscreen mode

So we might update first naive implementation from previous part with this code

either typescript

But its not required as we used pure Either in template. How? Look into directive in previous part

@Directive({ selector: '[fpIfRight]' })
export class IfRightDirective<TE = unknown, TD = unknown> {
  static ngTemplateGuard_fpIfRight: 'binding'; // * Static binding

  private _context: IfContext<TE | TD> = initialIfContext(); // *  Initialization
  private _refs = initialRefs();

  constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef<IfContext<TE | TD>>) {
    this._refs.viewContainer = viewContainer;
    this._refs.thenTemplateRef = templateRef;
  }

// * Directive's Input
  @Input()
  set fpIfRight(either: Either<TE, TD> | null) {
    if (either) {
      either
        .mapLeft(l => {
          this._context.ifTrue = false;
          this._context.$implicit = l;
        })
        .mapRight(r => {
          this._context.ifTrue = true;
          this._context.$implicit = r;
        });
    }
    updateView(this._context, this._refs);
  }

  @Input()
  set fpIfRightThen(templateRef: TemplateRef<IfContext<TE | TD>> | null) {
    this._refs.thenTemplateRef = templateRef;
    this._refs.thenViewRef = null;
    updateView(this._context, this._refs);
  }

// * Handling ELSE block in template
  @Input()
  set fpIfRightElse(templateRef: TemplateRef<IfContext<TE | TD>> | null) {
    this._refs.elseTemplateRef = templateRef;
    this._refs.elseViewRef = null;
    updateView(this._context, this._refs);
  }

// * Context validation magic
  static ngTemplateContextGuard<TE, TD>(
    _dir: IfRightDirective<TE, TD>,
    _ctx: any
  ): _ctx is IfContext<Exclude<TD, false | 0 | '' | null | undefined>> {
    return true;
  }
}

Enter fullscreen mode Exit fullscreen mode

Lets start from the usage of directive

usage in html

Here we declaring structural directive with if block, adding ELSE block for Left case (errorTpl) and specify variable with implicit context (let ok). This ok variable will have correct type even in VSCode syntax with Angular language service, just be sure that you specify strict in compiler options. It's available due to some code in our directive, see it later in topic.

  • Initialization
    Here we initializing default values for our template, should it be in empty state or not. The same as with ngIf implementation, yes, it's kind of empty until first value arrives.

  • Directive's Input
    Thats the place where we getting value from this line

<ng-container *fpIfRight="stations; else errorTpl; let ok">
Enter fullscreen mode Exit fullscreen mode
  • Handling ELSE block in template
    This block will be used when we will be handling Left state of our Either container. Unfortunately, it still not available for typecheck.

  • Static binding
    Thats part related to Ivy- we specifying that we will build template from our code

  • Context validation magic
    That's part I mostly like with Ivy. With Ivy magic we can declare for compiler what type supposed be used by structural directive.

All this typechecking gives us a lot of power for structural directives and custom templates.

GitHub logo Lonli-Lokli / functional_angular

Created with StackBlitz ⚡️

Discussion (0)