From d9b283732d9d58b49f1f0511a4ef392fbc701534 Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Mon, 23 Jun 2025 15:18:15 -0400
Subject: [PATCH 1/5] Create 0000-add-helper-manager-for-service.md
---
text/0000-add-helper-manager-for-service.md | 198 ++++++++++++++++++++
1 file changed, 198 insertions(+)
create mode 100644 text/0000-add-helper-manager-for-service.md
diff --git a/text/0000-add-helper-manager-for-service.md b/text/0000-add-helper-manager-for-service.md
new file mode 100644
index 0000000000..07bfc8f552
--- /dev/null
+++ b/text/0000-add-helper-manager-for-service.md
@@ -0,0 +1,198 @@
+---
+stage: accepted
+start-date: 2025-06-15T00:00:00.000Z # In format YYYY-MM-DDT00:00:00.000Z
+release-date: # In format YYYY-MM-DDT00:00:00.000Z
+release-versions:
+teams:
+ - framework
+ - typescript
+prs:
+ accepted: # Fill this in with the URL for the Proposal RFC PR
+project-link:
+suite:
+---
+
+
+# Service Helper Manager
+
+## Summary
+
+Add helper manager capabilities to `@ember/service`'s `service` export, enabling direct template usage while maintaining backward compatibility with existing service injection patterns.
+
+## Motivation
+
+Current service access in templates requires unnecessary boilerplate:
+
+```js
+import Component from '@glimmer/component';
+import { service } from '@ember/service';
+
+export default class extends Component {
+ @service theme;
+
+ // Boilerplate just to expose the service
+ get exposedTheme() {
+ return this.theme;
+ }
+}
+```
+
+```hbs
+{{this.exposedTheme.toggle}}
+```
+
+With helper manager capabilities, eliminate the boilerplate:
+
+```gjs
+import Component from '@glimmer/component';
+import { service } from '@ember/service';
+
+
+ {{#let (service "theme") as |theme|}}
+
+ {{/let}}
+
+
+export default class extends Component {}
+```
+
+## Detailed design
+
+### Enhanced `service` Export
+
+The `service` export gains helper manager capabilities while preserving existing patterns:
+
+```js
+// All existing usage unchanged
+@service theme;
+@service('user-preferences') prefs;
+theme = service('theme');
+```
+
+```gjs
+// New template helper usage
+
+ {{! Direct service access with let }}
+ {{#let (service "theme") as |theme|}}
+
+ {{/let}}
+
+ {{! Dynamic service names }}
+ {{#let (service @serviceName) as |dynamicService|}}
+ {{dynamicService.someMethod}}
+ {{/let}}
+
+ {{! Composition with iteration }}
+ {{#let (service "cart") as |cart|}}
+ {{#each cart.items as |item|}}
+ {{item.name}} - {{cart.formatPrice item.price}}
+ {{/each}}
+ {{/let}}
+
+ {{! Property access }}
+
+ Content here
+
+
+```
+
+### Implementation
+
+```js
+class ServiceHelperManager {
+ createHelper(state, { positional: [serviceName] }) {
+ return { service: getOwner(state).lookup(`service:${serviceName}`) };
+ }
+
+ getValue({ service }) {
+ return service;
+ }
+}
+```
+
+### Template-only Components
+
+Perfect for template-only components:
+
+```gjs
+
+ {{#let (service "cart") as |cart|}}
+ {{cart.totalItems}}
+ {{cart.formattedTotal}}
+ {{/let}}
+ {{yield}}
+
+```
+
+### Common Patterns
+
+```gjs
+
+ {{! Service method calls require let }}
+ {{#let (service "formatter") as |formatter|}}
+ {{formatter.currency @price}}
+
+ {{/let}}
+
+ {{! Multiple services }}
+ {{#let (service "cart") (service "theme") as |cart theme|}}
+
+
Items: {{cart.totalItems}}
+
+
+ {{/let}}
+
+```
+
+### TypeScript Support
+
+Full type inference:
+
+```gts
+
+ {{#let (service "theme") as |theme|}}
+ {{! TypeScript knows theme is ThemeService }}
+
+ {{/let}}
+
+```
+
+## How we teach this
+
+### Mental Model
+
+"Service gets you a service" - consistent whether in classes or templates. Template usage follows standard Handlebars patterns with `{{#let}}` for method calls.
+
+### Documentation
+
+Update `@ember/service` docs with template examples. Add Guides section on "Services in Templates" emphasizing the `{{#let}}` pattern for method calls.
+
+## Drawbacks
+
+- **API Surface**: Another way to access services
+- **Template Verbosity**: `{{#let}}` adds nesting for method calls
+
+## Alternatives
+
+Keep requiring backing class injection. Maintains separation but creates boilerplate.
+
+This leads to utilities like [service in reactiveweb](https://reactive.nullvoxpopuli.com/functions/resource_service.service.html)
+
+## Unresolved questions
+
+- Error handling for missing services?
+ - Would be more doable if we had try/catch in templates
+- Build-time vs runtime service validation?
+ - forbid dynamic service names?
From d819f83af75f652a0f942611faa50dfc26a9aa9d Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Mon, 23 Jun 2025 15:19:14 -0400
Subject: [PATCH 2/5] Update and rename 0000-add-helper-manager-for-service.md
to 1118-add-helper-manager-for-service.md
---
...er-for-service.md => 1118-add-helper-manager-for-service.md} | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename text/{0000-add-helper-manager-for-service.md => 1118-add-helper-manager-for-service.md} (98%)
diff --git a/text/0000-add-helper-manager-for-service.md b/text/1118-add-helper-manager-for-service.md
similarity index 98%
rename from text/0000-add-helper-manager-for-service.md
rename to text/1118-add-helper-manager-for-service.md
index 07bfc8f552..453cf51b18 100644
--- a/text/0000-add-helper-manager-for-service.md
+++ b/text/1118-add-helper-manager-for-service.md
@@ -7,7 +7,7 @@ teams:
- framework
- typescript
prs:
- accepted: # Fill this in with the URL for the Proposal RFC PR
+ accepted: https://github.com/emberjs/rfcs/pull/1118
project-link:
suite:
---
From 61e1a2b50019d0f7be803e3d77bbc2246543bef5 Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Mon, 23 Jun 2025 16:03:24 -0400
Subject: [PATCH 3/5] Apply suggestions from code review
---
text/1118-add-helper-manager-for-service.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/text/1118-add-helper-manager-for-service.md b/text/1118-add-helper-manager-for-service.md
index 453cf51b18..c5c483d16a 100644
--- a/text/1118-add-helper-manager-for-service.md
+++ b/text/1118-add-helper-manager-for-service.md
@@ -102,7 +102,7 @@ theme = service('theme');
{{/let}}
{{! Property access }}
-
+
Content here
From 936b557d370ac38cb37ff9ff597e5cb9b3ec7a6f Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Fri, 27 Jun 2025 14:41:16 -0400
Subject: [PATCH 4/5] Update 1118-add-helper-manager-for-service.md
---
text/1118-add-helper-manager-for-service.md | 23 +++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/text/1118-add-helper-manager-for-service.md b/text/1118-add-helper-manager-for-service.md
index c5c483d16a..2fae265498 100644
--- a/text/1118-add-helper-manager-for-service.md
+++ b/text/1118-add-helper-manager-for-service.md
@@ -75,13 +75,17 @@ export default class extends Component {}
The `service` export gains helper manager capabilities while preserving existing patterns:
```js
-// All existing usage unchanged
-@service theme;
-@service('user-preferences') prefs;
-theme = service('theme');
+class {
+ // All existing usage unchanged
+ @service theme;
+ @service('user-preferences') prefs;
+ theme = service('theme');
+}
```
```gjs
+import { service } from '@ember/service';
+
// New template helper usage
{{! Direct service access with let }}
@@ -111,6 +115,9 @@ theme = service('theme');
### Implementation
```js
+import { service } from '@ember/service';
+
+// roughly:
class ServiceHelperManager {
createHelper(state, { positional: [serviceName] }) {
return { service: getOwner(state).lookup(`service:${serviceName}`) };
@@ -120,6 +127,8 @@ class ServiceHelperManager {
return service;
}
}
+
+setHelperManager(ServiceHelperManager, service);
```
### Template-only Components
@@ -127,6 +136,8 @@ class ServiceHelperManager {
Perfect for template-only components:
```gjs
+import { service } from '@ember/service';
+
{{#let (service "cart") as |cart|}}
{{cart.totalItems}}
@@ -139,6 +150,8 @@ Perfect for template-only components:
### Common Patterns
```gjs
+import { service } from '@ember/service';
+
{{! Service method calls require let }}
{{#let (service "formatter") as |formatter|}}
@@ -161,6 +174,8 @@ Perfect for template-only components:
Full type inference:
```gts
+import { service } from '@ember/service';
+
{{#let (service "theme") as |theme|}}
{{! TypeScript knows theme is ThemeService }}
From 6690f14e1551a8099b54f79008834bd27d56b0e0 Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Sun, 13 Jul 2025 16:13:32 -0400
Subject: [PATCH 5/5] Mention how resources could help with implementation with
this later.
---
text/1118-add-helper-manager-for-service.md | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/text/1118-add-helper-manager-for-service.md b/text/1118-add-helper-manager-for-service.md
index 2fae265498..6d238a5a43 100644
--- a/text/1118-add-helper-manager-for-service.md
+++ b/text/1118-add-helper-manager-for-service.md
@@ -201,9 +201,15 @@ Update `@ember/service` docs with template examples. Add Guides section on "Serv
## Alternatives
-Keep requiring backing class injection. Maintains separation but creates boilerplate.
+- Keep requiring backing class injection. Maintains separation but creates boilerplate.
+ This leads to utilities like [service in reactiveweb](https://reactive.nullvoxpopuli.com/functions/resource_service.service.html)
+
+
+- There could be a future where services are user-land definable as _Resources_, as shown in [RFC #1122: Resources](https://github.com/emberjs/rfcs/pull/1122).|
+ Since resources _are_ helpers (by definition and implementation), the way we access singleton values on the application would be the same in JavaScript as in the component template.
+
+
-This leads to utilities like [service in reactiveweb](https://reactive.nullvoxpopuli.com/functions/resource_service.service.html)
## Unresolved questions