Angular: .ng.mx in detail
A .ng.mx file is a TypeScript module. One expression in it is MX: the value of template:. Everything else passes through untouched, and mx-angular writes the result as x.component.ts beside it.
The template goes in one place
The MX region is the direct value of template: in the first argument of @Component({ … }). Any other position (a wrapping call, a ternary, a nested object, another decorator) is an error that names this rule.
Everything outside template: is ordinary TypeScript: imports and types go at the top of the module, not in the template.
Several roots: a fragment
A region is one expression, so it has one root. An Angular template may have many; say so with <>…</>:
@Component({
selector: "[sortable-th]",
template: <>
<ng-content/>
<app-sort-indicator direction=direction/>
</>,
})
export class SortableTh {}
The children are emitted as siblings: no <ng-container> and no wrapper element, which matters for an attribute-selector component whose host element is not yours to wrap. A fragment is only valid as the root of the template, cannot nest, and <></> is an empty template. Two bare roots are an error that tells you to wrap them.
MX maintains imports:
Every MX tag the template calls, and every Angular directive the compiled template needs (NgClass, NgStyle, KeyValuePipe, NgComponentOutlet, NgTemplateOutlet), is added to the decorator’s imports: and given its import statement. A symbol you already listed is left alone. Hand-written components you call by selector are yours to import, as always.
standalone: false. Angular rejects imports: on such a component, so MX leaves the decorator alone and warns at the template with each symbol the declaring NgModule must provide.
Calling an MX tag
import Card from "./tags/card.mx";
// …
template: <Card><@title>Team</@title></Card>,
<Card> emits the tag’s selector (<mx-card>), one imports: entry, and the import rewritten to the generated class. A tag under a tags/ directory can also be called by its discovered name, <card>, with no import. The import must be a sole default import: import A, { b } from "./x.mx" used as <A/> is an error. See MX tags.
Event handlers
onClick=clear compiles to (click)="__mxOn(clear, this, $event)". The call goes through a small typed invoker so that a handler with no parameters, one, or Marko’s (event, element) all type-check under strictTemplates. MX adds the two invoker members (__mxOn, __mxOnAt) to your class. To own them instead, extend the base class and MX adds nothing:
import { MxHandlers, MxHandlersMixin } from "@mxlang/angular/runtime";
export class FormComponent extends MxHandlers {}
export class ListComponent extends MxHandlersMixin(PagedBase) {}
Extend them directly. Through an alias or a base class in another file, MX cannot see the members, adds its own, and TypeScript reports the clash (TS2415). Importing the runtime makes @mxlang/angular a dependencies entry, not a dev one. Details are in Events.
What the build writes
x.component.ng.mxbecomesx.component.ts(change the extension withmx.angular.ngExtension), with a generated header and a.mapfile beside it.- MX refuses to overwrite a file that does not carry its header, so a hand-written module is never clobbered.
.ng.mxfiles are discovered across the whole project;mx.angular.includedoes not apply to them.- The emitted
.tsis a build artifact: ignore it in git.
Editors
VS Code and Zed register .ng.mx: the module highlights as TypeScript and the template as MX. See VS Code and Zed.