Angular
Next release
How midcode runs ng serve, finds each element of an Angular page in the components' templates after it loads, and edits those templates, external or inline.
This ships with the next release of midcode. The version you can download today (1.1.2) doesn’t have it yet.
midcode edits an Angular app in its components’ templates: text, classes, attributes and structure, in .html templates and in the ones written inside a component’s .ts. Angular’s build takes no plugin from outside, so midcode doesn’t mark elements while the app compiles. It finds each element in your templates after the page has loaded. What Angular computes ({{ }}, [bound] values, @if and @for blocks) is left exactly as written. midcode 1.1.2 and earlier don’t edit Angular projects.
At a glance
| Angular | |
|---|---|
| Detected by | angular.json at the project’s root |
| Runs with | ./node_modules/.bin/ng serve --host 127.0.0.1 --port $PORT |
| Elements are marked by | Matching each loaded page against the components’ templates |
| Editing | Text, classes, attributes, tag; insert, move, duplicate, delete, wrap in a stack |
| Components | No instances or props panel. A component’s host element is selected like any element |
| Pages | The routes of app.routes.ts or a routing module |
| Styles | Your Tailwind 4 classes, or mid: utilities with public/midcode.css linked from src/index.html |
| Tried with | Angular 22 (standalone components, zoneless) |
How midcode runs it
A folder with an angular.json is an Angular project. midcode runs your project’s own CLI, in your login shell, from the project’s folder:
NG_CLI_ANALYTICS=false ./node_modules/.bin/ng serve --host 127.0.0.1 --port $PORT$PORT is a free port midcode picks, starting at 4310. NG_CLI_ANALYTICS=false keeps the CLI from stopping to ask about analytics, which nobody could answer. Nothing in angular.json, your package.json or your build changes. To run it another way, see Any other stack.
How elements get their marks
When a page loads in a frame, midcode reads its elements and looks each one up in the project’s templates. Where it’s sure, it sets data-mc="file:line:col" on the element in the page (never in your files), and from there the canvas treats it like any other marked element.
An element of the page is a written element when what’s fixed in the template is there in the page: its tag, its id, every class that isn’t computed, its text when text is all it holds, and attributes like href, src, alt, type or placeholder. midcode looks first among the children of what its parent turned out to be, then in the whole project, where only an unmistakable match counts. In doubt the element stays unmarked: it can’t be edited, and it’s never edited by mistake.
An Angular page keeps drawing itself: the router swaps views, an @if flips. midcode watches the page, and when elements arrive without a mark it matches the page again. After an edit it reloads nothing: Angular’s dev server swaps the template in place. Sites a server renders describes the matching in full; Angular goes the same way.
Which files are templates
A file named
*.component.htmlor*.ng.html, and, in an Angular project, any other.html(app.html,src/index.html).A component’s
.tswith its template inside (template: '…', in backticks or quotes). It’s read where it sits, so a mark points into the.tsfile. A template the code builds with${…}isn’t read.
Files like *.spec.ts, *.routes.ts, *.config.ts, *.service.ts and *.module.ts are never read for templates.
What you can edit
Take this template:
<section class="hero">
<h1>{{ title() }}</h1>
<p class="lead">Furniture made to order.</p>
@for (wood of woods(); track wood.id) {
<app-card [wood]="wood" class="card" />
}
</section>| Element | On the canvas |
|---|---|
<p class="lead"> | Everything: its text, its classes, its place |
<h1> | Classes and attributes. Its text is {{ title() }}, which isn’t written there |
<app-card> | Its written classes and attributes. [wood] shows as code. Written once and drawn for every item, so an edit changes them all |
What the reader takes as computed, and never rewrites:
{{ … }}, and ICU messages ({count, plural, …}).The blocks:
@if,@else,@for,@empty,@switch,@case,@default,@deferwith@placeholder,@loadingand@error, a@let x = …;, and the}that closes a block.[property],(event),[(both)],*structural,#referenceand@animationon a tag.
<ng-container>, <ng-template> and <ng-content> aren’t in the page, though what’s inside them is. Any other tag with a dash is a component’s host element: it’s in the page, and you can select it where it’s written.
Text
- <p class="lead">Furniture made to order.</p>+ <p class="lead">Furniture made to last.</p>In an Angular template a { opens a message, a } closes a block and an @ opens one, so midcode writes those three as entities: typing hello@oak.studio leaves hello@oak.studio in the file, which the page shows as you typed it.
Classes and attributes
The style panel writes into the static class attribute and creates it when there’s none. [class.active], [ngClass] and [class] are bindings and are left alone.
Attributes are read as written, and a value bound to code shows as code under the name it binds. A value is written the way Angular writes it: a text as placeholder="Your email", a switch that’s on as disabled, a number or other code as [maxlength]="40".
Inline templates
The same edits work inside a component’s .ts:
@Component({ selector: 'app-badge',- template: `<span class="badge">New</span>`,+ template: `<span class="badge">Just in</span>`, })Quotes, backticks and backslashes you type are written as entities there (It's here), so they can’t end the string. Before saving, midcode checks that everything outside the templates is unchanged. If an edit would reach the code around them, nothing is written and it says so: “That can’t be written inside this component’s code as it is: it would end its template. Edit it in the code.”
Structure
Insert adds plain markup, an element is dragged before, after or inside another one in the same file (⌥-drag leaves a copy there instead), and several can be deleted, moved or wrapped in a stack (⇧A) at once.
Pages
The page list comes from the files where the app lists its routes: *.routes.ts, *-routing.module.ts and *.routing.ts under src/, the app’s own first. Every path: '…' is a page, under the paths of the routes it’s a child of:
| Route | Page |
|---|---|
{ path: '', component: Home } | The home page |
{ path: 'work', loadComponent: () => import('./work/work') } | /work |
{ path: 'work/:id', component: Project } | /work/[id], a dynamic route |
{ path: '**', component: NotFound } | Not a page |
When a route’s component can be followed through its import, the page knows its template file. A dynamic route has no address of its own: open one of its pages by typing a real path in the page menu in the top bar (“Search, or type a path and press Enter”). Pages and navigation has the rest.
midcode doesn’t add pages to an Angular app: a route is a line of code and a component, yours or your agent’s to write.
Site settings
The globe in the top bar opens Site settings. The title, the description, the language, search engines, the favicon and the social image are written as tags in the <head> of src/index.html, and the images are copied into public/. There’s one form for the whole app. This wasn’t tried on an Angular project.
Styles
With Tailwind 4 through PostCSS, which is what ng new --style=tailwind sets up, the panel writes your own utilities: see Tailwind CSS.
Without it, midcode writes its own prefixed utilities (mid:p-6, mid:md:flex) and keeps their plain CSS in public/midcode.css. The first style edit creates the file and links it from the page every route goes through, as one undoable step:
<link rel="icon" type="image/x-icon" href="favicon.ico">+ <link rel="stylesheet" href="/midcode.css"> </head>These are global rules, each one !important, so they apply inside every component whatever its view encapsulation. Your components’ own styles aren’t edited. Without Tailwind has the details.
Limits
On a page with a header, a router outlet,
@for,@if/@else, a component with an inline template and projected content, 48 of 49 elements were found. The missing one is the host element the router creates, which isn’t written anywhere.Tried on that app: text, classes and attributes, in external and inline templates. Not tried: apps built on NgModules, server-side rendering, component libraries such as Angular Material, and one
templateUrlshared by several components.What a library draws has its template in
node_modules, which midcode doesn’t read: those elements stay unmarked.midcode reads a block’s opening and its closing
}as markers, not as a container. An element that uses a@for’s item can be dragged out of the block, where that name no longer exists. ⌘Z puts it back.Routes of a lazy-loaded routing file show without the prefix they’re mounted under.
Styles without Tailwind need the
public/folder Angular has served since version 18. An older project, with its assets listed inangular.json, wasn’t tried. New images are copied intopublic/too, and without that folder Site settings can’t place a favicon or a social image: it asks you to put the file where the site serves it and link it in the code.An insert that needs an import is refused, and Insert’s interactive components are React (“Needs a React page”).
There are no component instances here, so no props, variants or states. The free canvas is for Next.js projects on the App Router.
Troubleshooting
An element can be selected but not edited. midcode didn’t find it in a template with certainty. That happens when several written elements could be it and nothing fixed tells them apart: give it a class or an id of its own. It also happens when the element isn’t written in your templates at all (a library’s, or one a script adds).
The server didn’t start. Open the log with View log on the card. “Change how it runs” under it lets you give another command, such as your own npm start; midcode keeps it in .midcode/server.json (Any other stack).