diff --git a/changelog_unreleased/angular/15968.md b/changelog_unreleased/angular/15968.md
new file mode 100644
index 000000000..55b8047a0
--- /dev/null
+++ b/changelog_unreleased/angular/15968.md
@@ -0,0 +1,32 @@
+#### Support Angular inline styles as single template literal (#15968 by @sosukesuzuki)
+
+[Angular v17](https://blog.angular.io/introducing-angular-v17-4d7033312e4b) supports single string inline styles.
+
+
+```ts
+// Input
+@Component({
+ template: `
...
`,
+ styles: `h1 { color: blue; }`,
+})
+export class AppComponent {}
+
+// Prettier stable
+@Component({
+ template: `...
`,
+ styles: `h1 { color: blue; }`,
+})
+export class AppComponent {}
+
+// Prettier main
+@Component({
+ template: `...
`,
+ styles: `
+ h1 {
+ color: blue;
+ }
+ `,
+})
+export class AppComponent {}
+
+```
diff --git a/src/language-js/embed/utils.js b/src/language-js/embed/utils.js
index eb87cf70d..107fe490b 100644
--- a/src/language-js/embed/utils.js
+++ b/src/language-js/embed/utils.js
@@ -30,15 +30,25 @@ const angularComponentObjectExpressionPredicates = [
* })
*/
function isAngularComponentStyles(path) {
- return path.match(
- (node) => node.type === "TemplateLiteral",
- (node, name) => isArrayOrTupleExpression(node) && name === "elements",
- (node, name) =>
- isObjectProperty(node) &&
- node.key.type === "Identifier" &&
- node.key.name === "styles" &&
- name === "value",
- ...angularComponentObjectExpressionPredicates,
+ const isTemplateLiteral = (node) => node.type === "TemplateLiteral";
+ const isObjectPropertyNamedStyles = (node, key) =>
+ isObjectProperty(node) &&
+ !node.computed &&
+ node.key.type === "Identifier" &&
+ node.key.name === "styles" &&
+ key === "value";
+ return (
+ path.match(
+ isTemplateLiteral,
+ (node, name) => isArrayOrTupleExpression(node) && name === "elements",
+ isObjectPropertyNamedStyles,
+ ...angularComponentObjectExpressionPredicates,
+ ) ||
+ path.match(
+ isTemplateLiteral,
+ isObjectPropertyNamedStyles,
+ ...angularComponentObjectExpressionPredicates,
+ )
);
}
function isAngularComponentTemplate(path) {
diff --git a/tests/format/typescript/angular-component-examples/15934-computed.component.ts b/tests/format/typescript/angular-component-examples/15934-computed.component.ts
new file mode 100644
index 000000000..653f497f3
--- /dev/null
+++ b/tests/format/typescript/angular-component-examples/15934-computed.component.ts
@@ -0,0 +1,13 @@
+import { Component } from '@angular/core';
+
+const styles = "foobar";
+
+@Component({
+ selector: 'app-root',
+ template: `
+ My App
+
+ `,
+ [styles]: `h1 { color: blue }`
+})
+export class AppComponent {}
diff --git a/tests/format/typescript/angular-component-examples/15934.component.ts b/tests/format/typescript/angular-component-examples/15934.component.ts
new file mode 100644
index 000000000..c6c4d66dc
--- /dev/null
+++ b/tests/format/typescript/angular-component-examples/15934.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-root',
+ template: `
+ My App
+
+ `,
+ styles: `h1 { color: blue }`
+})
+export class AppComponent {}
diff --git a/tests/format/typescript/angular-component-examples/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/angular-component-examples/__snapshots__/jsfmt.spec.js.snap
index 21e4afe34..9a41ce0f2 100644
--- a/tests/format/typescript/angular-component-examples/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/angular-component-examples/__snapshots__/jsfmt.spec.js.snap
@@ -1,5 +1,161 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`15934.component.ts - {"trailingComma":"es5"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "es5"
+ | printWidth
+=====================================input======================================
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-root',
+ template: \`
+ My App
+
+ \`,
+ styles: \`h1 { color: blue }\`
+})
+export class AppComponent {}
+
+=====================================output=====================================
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "app-root",
+ template: \`
+ My App
+
+ \`,
+ styles: \`
+ h1 {
+ color: blue;
+ }
+ \`,
+})
+export class AppComponent {}
+
+================================================================================
+`;
+
+exports[`15934.component.ts - {"trailingComma":"none"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "none"
+ | printWidth
+=====================================input======================================
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-root',
+ template: \`
+ My App
+
+ \`,
+ styles: \`h1 { color: blue }\`
+})
+export class AppComponent {}
+
+=====================================output=====================================
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "app-root",
+ template: \`
+ My App
+
+ \`,
+ styles: \`
+ h1 {
+ color: blue;
+ }
+ \`
+})
+export class AppComponent {}
+
+================================================================================
+`;
+
+exports[`15934-computed.component.ts - {"trailingComma":"es5"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "es5"
+ | printWidth
+=====================================input======================================
+import { Component } from '@angular/core';
+
+const styles = "foobar";
+
+@Component({
+ selector: 'app-root',
+ template: \`
+ My App
+
+ \`,
+ [styles]: \`h1 { color: blue }\`
+})
+export class AppComponent {}
+
+=====================================output=====================================
+import { Component } from "@angular/core";
+
+const styles = "foobar";
+
+@Component({
+ selector: "app-root",
+ template: \`
+ My App
+
+ \`,
+ [styles]: \`h1 { color: blue }\`,
+})
+export class AppComponent {}
+
+================================================================================
+`;
+
+exports[`15934-computed.component.ts - {"trailingComma":"none"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "none"
+ | printWidth
+=====================================input======================================
+import { Component } from '@angular/core';
+
+const styles = "foobar";
+
+@Component({
+ selector: 'app-root',
+ template: \`
+ My App
+
+ \`,
+ [styles]: \`h1 { color: blue }\`
+})
+export class AppComponent {}
+
+=====================================output=====================================
+import { Component } from "@angular/core";
+
+const styles = "foobar";
+
+@Component({
+ selector: "app-root",
+ template: \`
+ My App
+
+ \`,
+ [styles]: \`h1 { color: blue }\`
+})
+export class AppComponent {}
+
+================================================================================
+`;
+
exports[`test.component.ts - {"trailingComma":"es5"} format 1`] = `
====================================options=====================================
parsers: ["typescript"]