Skip to content

Commit 0323092

Browse files
committed
Deprecate global controller lookup
1 parent d058ab0 commit 0323092

6 files changed

Lines changed: 525 additions & 108 deletions

File tree

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,46 @@
1-
export function identifierForController(controller: any, ident: any): string;
21
/**
3-
* The {@link ng.$controller $controller service} is used by AngularTS to create new
4-
* controllers.
5-
*
6-
* This provider allows controller registration via the
7-
* {@link ng.$controllerProvider#register register} method.
2+
* @param {string | InjectableController | undefined} controller
3+
* @param {string} [ident]
4+
* @returns {string|undefined}
85
*/
6+
export function identifierForController(
7+
controller: string | InjectableController | undefined,
8+
ident?: string,
9+
): string | undefined;
910
export class ControllerProvider {
10-
/**
11-
* @type {Map<string, Function|Object>}
12-
* @private
13-
*/
11+
/** @type {Map<string, InjectableController>} @private */
1412
private controllers;
15-
/**
16-
* Check if a controller with a given name exists.
17-
*
18-
* @param {string} name Controller name to check.
19-
* @returns {boolean} True if the controller exists, false otherwise.
20-
*/
13+
/** @param {string} name @returns {boolean} */
2114
has(name: string): boolean;
2215
/**
23-
* Register a controller.
24-
*
25-
* @param {string|Object} name Controller name, or an object map of controllers where the keys are
26-
* the names and the values are the constructors.
27-
* @param {Function|Array} constructor Controller constructor function (optionally decorated with DI
28-
* annotations in the array notation).
16+
* @param {string | Record<string, unknown>} name
17+
* @param {unknown} [constructor]
2918
*/
30-
register(name: string | any, constructor: Function | any[]): void;
19+
register(name: string | Record<string, unknown>, constructor?: unknown): void;
3120
/**
32-
* $get method for dependency injection.
21+
* @type {import("../../interface.ts").AnnotatedFactory<(injector: ng.InjectorService) => ControllerService>}
3322
*/
34-
$get: (
35-
| string
36-
| ((
37-
$injector: ng.InjectorService,
38-
) => import("./interface.ts").ControllerService)
39-
)[];
23+
$get: import("../../interface.ts").AnnotatedFactory<
24+
(injector: ng.InjectorService) => ControllerService
25+
>;
4026
/**
41-
* Adds an identifier to the controller instance in the given locals' scope.
42-
*
43-
* @param {Object} locals The locals object containing the scope.
44-
* @param {string} identifier The identifier to assign.
45-
* @param {Object} instance The controller instance.
46-
* @param {string} name The name of the controller.
27+
* @param {ControllerLocals | undefined} locals
28+
* @param {string} identifier
29+
* @param {object} instance
30+
* @param {string} name
4731
*/
4832
addIdentifier(
49-
locals: any,
33+
locals: ControllerLocals | undefined,
5034
identifier: string,
51-
instance: any,
35+
instance: object,
5236
name: string,
5337
): void;
5438
}
39+
export type ControllerConstructor =
40+
import("../../interface.ts").ControllerConstructor;
41+
export type InjectableController =
42+
import("../../interface.ts").Injectable<ControllerConstructor>;
43+
export type ControllerService = import("./interface.ts").ControllerService;
44+
export type ControllerLocals = import("./interface.ts").ControllerLocals;
45+
export type ControllerExpression =
46+
import("./interface.ts").ControllerExpression;
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import { ControllerConstructor, Injectable } from "../../interface.ts";
12
export type ControllerService = (
2-
expression: string | Function | ng.AnnotatedFactory<any>,
3-
locals?: Record<string, any>,
3+
expression: ControllerExpression,
4+
locals?: ControllerLocals,
45
later?: boolean,
56
ident?: string,
67
) => object | (() => object);
8+
export type ControllerExpression = string | Injectable<ControllerConstructor>;
9+
export type ControllerLocals = Record<string, any> & {
10+
$scope?: ng.Scope;
11+
};
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# ControllerProvider — Design Notes
2+
3+
## Purpose
4+
5+
`ControllerProvider` implements AngularTS’s `$controller` mechanism. It is responsible for:
6+
7+
- registering controller definitions during configuration
8+
- resolving controller expressions at runtime
9+
- instantiating controllers with DI support
10+
- supporting `ctrl as` syntax and explicit identifiers
11+
- optionally deferring instantiation (`later` mode)
12+
13+
The design closely mirrors AngularJS semantics while tightening types and removing legacy behaviors (e.g. global/window lookup).
14+
15+
---
16+
17+
## Core Concepts
18+
19+
### Controller Definition
20+
21+
A **controller definition** is normalized at registration time into an:
22+
23+
```
24+
Injectable<ControllerConstructor>
25+
```
26+
27+
Supported forms:
28+
- constructor function
29+
- ES class
30+
- DI-annotated array (`['dep1', ..., ControllerFn]`)
31+
32+
Unsupported:
33+
- plain objects
34+
- non-callables
35+
36+
This invariant is enforced by `normalizeControllerDef()` and relied on everywhere else.
37+
38+
---
39+
40+
## Internal State
41+
42+
### `controllers: Map<string, InjectableController>`
43+
44+
- Keys are **literal controller names**
45+
- Values are **normalized injectables**
46+
- No fallback to `window` or dotted-path resolution
47+
- Dotted names (`"a.b.Ctrl"`) are treated as plain keys
48+
49+
---
50+
51+
## Registration Phase
52+
53+
### `register(name, constructor)`
54+
55+
Supports:
56+
- single controller registration
57+
- bulk registration
58+
59+
Behavior:
60+
- controller names are validated
61+
- definitions are normalized eagerly
62+
- invalid definitions throw `ctrlreg`
63+
64+
---
65+
66+
## Runtime: `$controller` Service
67+
68+
### Signature (conceptual)
69+
70+
```
71+
$controller(expression, locals?, later?, ident?)
72+
→ instance | () => instance
73+
```
74+
75+
### Expression Resolution
76+
77+
If `expression` is a **string**:
78+
- must match `CNTRL_REG`
79+
- extracts controller name and optional `as` identifier
80+
- resolves only from the internal registry
81+
- throws `ctrlfmt` or `ctrlreg` on failure
82+
83+
If `expression` is **not a string**:
84+
- treated as a controller injectable directly
85+
86+
---
87+
88+
## Controller Unwrapping
89+
90+
### `unwrapController(injectable)`
91+
92+
Central helper that:
93+
- extracts the underlying constructor function
94+
- asserts callability
95+
- exposes:
96+
- `func`
97+
- `prototype`
98+
- `name`
99+
100+
This removes duplicated logic and provides a single point of truth.
101+
102+
---
103+
104+
## Instantiation Modes
105+
106+
### Normal Mode
107+
108+
- controller instantiated immediately via `$injector.instantiate`
109+
- object/function return values replace the instance
110+
- primitive return values are ignored
111+
- identifier export happens after instantiation
112+
113+
### Deferred Mode (`later === true`)
114+
115+
- instance shell created via `Object.create(prototype)`
116+
- identifier export happens before invocation
117+
- returned function invokes the controller and may replace the instance
118+
- identifier is rebound if replacement occurs
119+
120+
---
121+
122+
## Identifier (`ctrl as`) Handling
123+
124+
Sources of identifier (priority order):
125+
1. explicit `ident` argument
126+
2. `as` syntax in controller string
127+
128+
Rules:
129+
- identifier requires `locals.$scope`
130+
- exports instance to scope and sets `$controllerIdentifier`
131+
- missing `$scope` throws `noscp`
132+
133+
---
134+
135+
## Locals Handling
136+
137+
- `locals` is a DI locals map
138+
- `$scope` is optional unless exporting
139+
- locals override injectable dependencies
140+
- no implicit scope creation
141+
142+
---
143+
144+
## `$scopename` Propagation
145+
146+
If a controller defines `$scopename`, it is propagated to `locals.$scope`
147+
in both normal and deferred modes when a scope is present.
148+
149+
---
150+
151+
## Error Semantics
152+
153+
| Code | Condition |
154+
|----------|-----------|
155+
| ctrlfmt | malformed controller string |
156+
| ctrlreg | unknown controller or invalid definition |
157+
| noscp | identifier export without `$scope` |
158+
| badname | illegal controller registration name |
159+
160+
---
161+
162+
## Design Invariants
163+
164+
- controllers are always normalized
165+
- no global/window lookup
166+
- deferred mode preserves prototype semantics
167+
- identifier export is explicit and scoped
168+
169+
---
170+
171+
## Non-Goals
172+
173+
- no object-literal controllers
174+
- no dotted-path resolution
175+
- no implicit scope creation
176+
- no silent fallbacks
177+
178+
---
179+
180+
## Summary
181+
182+
`ControllerProvider` favors explicitness and predictability:
183+
- strict registration
184+
- centralized normalization
185+
- well-defined error cases
186+
- compatibility where it matters, simplicity everywhere else

0 commit comments

Comments
 (0)