Skip to content

Bindings

Attributes, property bindings, events and two-way — the four Angular channels, kept apart.

Angular has four ways to pass something to a component, and they mean four different things. Blasdoc keeps them apart — in the parser, in the IR and at runtime — because collapsing them into one bag of props would destroy the semantics and make an accurate source rendering impossible.

The four channels

SourceMeaningApplied as
variant="primary"static attributean input when declared, a host attribute otherwise
[variant]="tone"property bindingthe evaluated value, set as an input
(clicked)="buy()"event bindinga subscription to the output
[(value)]="name"two-wayinput plus valueChange

The context

Every expression is evaluated against one object — and only that object.

On a page, the class itself:

ts
@Component({ selector: 'app-buy-page', templateMD, components: [AppButton] })
export class BuyPage {
  count = 0;
  locked = false;
 
  buy(sku: string): void {
    this.count++;
  }
}

Rendering a string instead? Hand the context in:

ts
provideBlasdoc({
  context: { count: 0, buy: (sku: string) => cart.add(sku) },
});
html
<blasdoc-content [source]="markdown" [context]="context" />

Static attributes

md
<app-button variant="primary">Comprar</app-button>

When the component declares an input of that name, it is set as an input. When it does not, it lands on the host element as an attribute — the same split Angular applies in a template.

Property bindings

md
<app-button [disabled]="count === 0">Checkout</app-button>

The expression is re-evaluated whenever something in the document changes it.

Event bindings

md
<app-button (clicked)="bump()">Pressed {{ count }} times</app-button>

$event is available, and a statement may assign:

md
<app-button (clicked)="locked = !locked">Toggle</app-button>
<app-input (valueChange)="name = $event" />

Two-way

md
<app-switch [(checked)]="locked" />

A two-way binding requires a real …Change output. When the component does not declare one, Blasdoc reports it rather than silently listening for an event that will never fire.

Bindings on plain HTML

They are legitimate Angular, so they work here too — applied as attributes:

md
<div [title]="'Pressed ' + count + ' times'">Hover me</div>

The expression language

Expressions are interpreted, never compiled. There is no eval and no new Function: an expression is parsed into an AST and walked by an evaluator that can reach your context object and the call's locals, and nothing else.

Supported:

text
paths          user.name, items[0].title
calls          buy('sku-1'), format(total, 2)
literals       'text', 42, true, null, [1, 2], { a: 1 }
operators      + - * / %  === !== < <= > >=  && || ??  !
ternary        count > 0 ? 'ready' : 'empty'
optional       user?.profile?.name
assignment     locked = !locked

Not supported: pipes, new, arrow functions, await, and anything that would amount to running arbitrary code.

When the view refreshes

Re-evaluation is triggered by events inside the document. A context mutated from outside it needs a new context reference, or a contextChange, to refresh.