Analog SFCs
Note:
This file format and API is experimental, is a community-driven initiative, and is not an officially proposed change to Angular. Use it at your own risk.
The .analog
file extension denotes a new file format for Single File Components (SFCs) that aims to simplify the authoring experience and provide Angular-compatible components and directives.
Together, it combines:
- Colocated template, script, and style tags
- Use of Angular Signal APIs without decorators
- Performance-first defaults (
OnPush
change detection, no accesss tongDoCheck
, etc.)
Usage
To use the Analog SFC, you need to use the Analog Vite plugin or the Analog Astro plugin with an additional flag to enable its usage:
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';
export default defineConfig({
// ...
plugins: [
analog({
vite: {
// Required to use the Analog SFC format
experimental: {
supportAnalogFormat: true,
},
},
}),
],
});
You must also uncomment the type information in the
src/vite-env.d.ts
file. This is temporary while the Analog SFC is experimental.
Additional Configuration
If you are using .analog
files outside a project's root you need to specify all paths of .analog
files using globs, like so:
export default defineConfig(({ mode }) => ({
// ...
plugins: [
analog({
vite: {
experimental: {
supportAnalogFormat: {
include: ['/libs/shared/ui/**/*', '/libs/some-lib/ui/**/*'],
},
},
},
}),
],
}));
IDE Support
To support syntax highlighting and other IDE functionality with .analog
files, you need to install an extension to support the format for:
Authoring an SFC
Here's a demonstration of the Analog format building a simple counter:
<script lang="ts">
// counter.analog
import { signal } from '@angular/core';
const count = signal(0);
function add() {
count.set(count() + 1);
}
</script>
<template>
<div class="container">
<button (click)="add()">{{count()}}</button>
</div>
</template>
<style>
.container {
display: flex;
justify-content: center;
}
button {
font-size: 2rem;
padding: 1rem 2rem;
border-radius: 0.5rem;
background-color: #f0f0f0;
border: 1px solid #ccc;
}
</style>
See the defineMetadata section for adding additional component metadata.
Metadata
While class decorators are used to add metadata to a component or directive in the traditional Angular authoring methods, they're replaced in the Analog format with the defineMetadata
global function:
defineMetadata({
host: { class: 'block articles-toggle' },
});
This supports all of the decorator properties of @Component
or @Directive
with a few exceptions.
Disallowed Metadata Properties
The following properties are not allowed on the metadata fields:
template
: Use the SFC<template>
ordefineMetadata.templateUrl
insteadstandalone
: Always set totrue
changeDetection
: Always set toOnPush
styles
: Use the SFC<style>
tagoutputs
: Use theoutput
signal API insteadinputs
: Use theinput
signal API instead
Host Metadata
As shown above, you can add host metadata to your component using the host
field:
defineMetadata({
host: { class: 'block articles-toggle' },
});
Another way to add host metadata is to use the <template>
tag
<template class="block articles-toggle"></template>
You can also have Property Binding and Event Binding in the <template>
tag:
<script lang="ts">
import { signal } from '@angular/core';
const bg = signal('black');
function handleClick() {}
</script>
<template [style.backgroundColor]="bg()" (click)="handleClick()"></template>
Using an External Template and Styles
If you like the developer experience of Analog's <script>
to build your logic, but don't want your template and styling in the same file, you can break those out to their own files using:
templateUrl
styleUrl
styleUrls
In defineMetadata
, like so:
<script lang="ts">
defineMetadata({
selector: 'app-root',
templateUrl: './test.html',
styleUrl: './test.css',
});
onInit(() => {
alert('Hello World');
});
</script>
Using Components
When using the Analog format, you do not need to explicitly export anything; the component is the default export of the .analog
file:
import { bootstrapApplication } from '@angular/platform-browser';
import App from './app/app.analog';
import { appConfig } from './app/app.config';
bootstrapApplication(App, appConfig).catch((err) => console.error(err));
To use the components you need to add them to your imports
(alternatively, you can use import attributes as explained in the following section):
<!-- layout.analog -->
<script lang="ts">
import { inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AuthStore } from '../shared-data-access-auth/auth.store';
import LayoutFooter from '../ui-layout/layout-footer.analog';
import LayoutHeader from '../ui-layout/layout-header.analog';
defineMetadata({ imports: [RouterOutlet, LayoutFooter, LayoutHeader] });
const authStore = inject(AuthStore);
</script>
<template>
<LayoutHeader
[isAuthenticated]="authStore.isAuthenticated()"
[username]="authStore.username()"
/>
<router-outlet />
<LayoutFooter />
</template>
A component's
selector
is not determined by the imported name, but rather determined by the name of the file. If you change your imported name to:<script lang="ts">
import LayoutHeaderHeading from '../ui-layout/layout-header.analog';
</script>
<template>
<LayoutHeaderHeading />
</template>It would not work as expected. To solve this, you'll need the name of the default import to match the file name of the
.analog
file.An official solution for this problem, from Angular, has been hinted by the Angular team and may come in a future version of Angular.