diff --git a/code/frameworks/angular/src/client/angular-beta/ComputesTemplateFromComponent.test.ts b/code/frameworks/angular/src/client/angular-beta/ComputesTemplateFromComponent.test.ts
index fca3ee9e37b4..e4542e26510c 100644
--- a/code/frameworks/angular/src/client/angular-beta/ComputesTemplateFromComponent.test.ts
+++ b/code/frameworks/angular/src/client/angular-beta/ComputesTemplateFromComponent.test.ts
@@ -1,9 +1,43 @@
import { Component } from '@angular/core';
import { ArgTypes } from 'storybook/internal/types';
import { describe, it, expect } from 'vitest';
-import { computesTemplateSourceFromComponent } from './ComputesTemplateFromComponent';
+import {
+ computesTemplateFromComponent,
+ computesTemplateSourceFromComponent,
+} from './ComputesTemplateFromComponent';
import { ISomeInterface, ButtonAccent, InputComponent } from './__testfixtures__/input.component';
+describe('angular template decorator', () => {
+ it('with props should generate tag with properties', () => {
+ const component = InputComponent;
+ const props = {
+ isDisabled: true,
+ label: 'Hello world',
+ accent: ButtonAccent.High,
+ counter: 4,
+ 'aria-label': 'Hello world',
+ };
+ const source = computesTemplateFromComponent(component, props);
+ expect(source).toEqual(
+ ``
+ );
+ });
+
+ it('with props should generate tag with outputs', () => {
+ const component = InputComponent;
+ const props = {
+ isDisabled: true,
+ label: 'Hello world',
+ onClick: ($event: any) => {},
+ 'dash-out': ($event: any) => {},
+ };
+ const source = computesTemplateFromComponent(component, props);
+ expect(source).toEqual(
+ ``
+ );
+ });
+});
+
describe('angular source decorator', () => {
it('With no props should generate simple tag', () => {
const component = InputComponent;
@@ -264,6 +298,7 @@ describe('angular source decorator', () => {
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(``);
});
+
it('With props should generate tag with properties', () => {
const component = InputComponent;
const props = {
@@ -271,11 +306,12 @@ describe('angular source decorator', () => {
label: 'Hello world',
accent: ButtonAccent.High,
counter: 4,
+ 'aria-label': 'Hello world',
};
const argTypes: ArgTypes = {};
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(
- ``
+ ``
);
});
@@ -285,11 +321,12 @@ describe('angular source decorator', () => {
isDisabled: true,
label: 'Hello world',
onClick: ($event: any) => {},
+ 'dash-out': ($event: any) => {},
};
const argTypes: ArgTypes = {};
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(
- ``
+ ``
);
});
diff --git a/code/frameworks/angular/src/client/angular-beta/ComputesTemplateFromComponent.ts b/code/frameworks/angular/src/client/angular-beta/ComputesTemplateFromComponent.ts
index 7c585b8ff489..716b2cf9cb78 100644
--- a/code/frameworks/angular/src/client/angular-beta/ComputesTemplateFromComponent.ts
+++ b/code/frameworks/angular/src/client/angular-beta/ComputesTemplateFromComponent.ts
@@ -7,6 +7,20 @@ import {
getComponentInputsOutputs,
} from './utils/NgComponentAnalyzer';
+/**
+ * Check if the name matches the criteria for a valid identifier.
+ * A valid identifier can only contain letters, digits, underscores, or dollar signs.
+ * It cannot start with a digit.
+ */
+const isValidIdentifier = (name: string): boolean => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
+
+/**
+ * Returns the property name, if it can be accessed with dot notation. If not,
+ * it returns `this['propertyName']`.
+ */
+export const formatPropInTemplate = (propertyName: string) =>
+ isValidIdentifier(propertyName) ? propertyName : `this['${propertyName}']`;
+
const separateInputsOutputsAttributes = (
ngComponentInputsOutputs: ComponentInputsOutputs,
props: ICollection = {}
@@ -50,10 +64,12 @@ export const computesTemplateFromComponent = (
);
const templateInputs =
- initialInputs.length > 0 ? ` ${initialInputs.map((i) => `[${i}]="${i}"`).join(' ')}` : '';
+ initialInputs.length > 0
+ ? ` ${initialInputs.map((i) => `[${i}]="${formatPropInTemplate(i)}"`).join(' ')}`
+ : '';
const templateOutputs =
initialOutputs.length > 0
- ? ` ${initialOutputs.map((i) => `(${i})="${i}($event)"`).join(' ')}`
+ ? ` ${initialOutputs.map((i) => `(${i})="${formatPropInTemplate(i)}($event)"`).join(' ')}`
: '';
return buildTemplate(
@@ -137,7 +153,7 @@ export const computesTemplateSourceFromComponent = (
: '';
const templateOutputs =
initialOutputs.length > 0
- ? ` ${initialOutputs.map((i) => `(${i})="${i}($event)"`).join(' ')}`
+ ? ` ${initialOutputs.map((i) => `(${i})="${formatPropInTemplate(i)}($event)"`).join(' ')}`
: '';
return buildTemplate(ngComponentMetadata.selector, '', templateInputs, templateOutputs);
diff --git a/code/frameworks/angular/src/client/angular-beta/__testfixtures__/input.component.ts b/code/frameworks/angular/src/client/angular-beta/__testfixtures__/input.component.ts
index abf4205eeaf5..7cc025a3ae87 100644
--- a/code/frameworks/angular/src/client/angular-beta/__testfixtures__/input.component.ts
+++ b/code/frameworks/angular/src/client/angular-beta/__testfixtures__/input.component.ts
@@ -39,9 +39,13 @@ export class InputComponent {
@Input()
public label: string;
+ @Input('aria-label') public ariaLabel: string;
+
/** Specifies some arbitrary object */
@Input() public someDataObject: ISomeInterface;
@Output()
public onClick = new EventEmitter();
+
+ @Output('dash-out') public dashOut = new EventEmitter();
}
diff --git a/code/frameworks/angular/src/client/argsToTemplate.test.ts b/code/frameworks/angular/src/client/argsToTemplate.test.ts
index b7405aba645a..29a51acb1b9d 100644
--- a/code/frameworks/angular/src/client/argsToTemplate.test.ts
+++ b/code/frameworks/angular/src/client/argsToTemplate.test.ts
@@ -100,4 +100,10 @@ describe('argsToTemplate', () => {
const result = argsToTemplate(args, {});
expect(result).toEqual('[input]="input" (event1)="event1($event)"');
});
+
+ it('should format for non dot notation', () => {
+ const args = { 'non-dot': 'Value1', 'dash-out': () => {} };
+ const result = argsToTemplate(args, {});
+ expect(result).toEqual('[non-dot]="this[\'non-dot\']" (dash-out)="this[\'dash-out\']($event)"');
+ });
});
diff --git a/code/frameworks/angular/src/client/argsToTemplate.ts b/code/frameworks/angular/src/client/argsToTemplate.ts
index 0072aa84743d..5b29b627029f 100644
--- a/code/frameworks/angular/src/client/argsToTemplate.ts
+++ b/code/frameworks/angular/src/client/argsToTemplate.ts
@@ -1,3 +1,5 @@
+import { formatPropInTemplate } from './angular-beta/ComputesTemplateFromComponent';
+
/**
* Options for controlling the behavior of the argsToTemplate function.
*
@@ -68,7 +70,9 @@ export function argsToTemplate>(
return true;
})
.map(([key, value]) =>
- typeof value === 'function' ? `(${key})="${key}($event)"` : `[${key}]="${key}"`
+ typeof value === 'function'
+ ? `(${key})="${formatPropInTemplate(key)}($event)"`
+ : `[${key}]="${formatPropInTemplate(key)}"`
)
.join(' ');
}