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>;
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()
Either#mapLeft
function mapLeft<L, R, NewL>(fn: (val: L) => NewL): Either<NewL, R>;
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"
So we might update first naive implementation from previous part with this code
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;
}
}
Lets start from the usage of directive
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">
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 codeContext 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.
Top comments (0)