Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion CLAUDE.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions context/diagrams/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ watcher or expression never crashes the loop.
| [Controllers ($controller / $controllerProvider)](./controller.md) | `register` (config) → `$controller(name, locals, ident, later?)`, `'Name as alias'` parse, `Object.create` + `injector.invoke` + return-value replacement, `controllerAs` publish, the compiler's `later:true` seam |
| [DOM compiler ($compile)](./compile.md) | `$compile(element)` walk → directive collect/sort → compile → three-phase link (pre/child/post); the controller seam, transclusion, isolate bindings, text/attr interpolation, `templateUrl`, errors via `$exceptionHandler('$compile')` |
| [Built-in directives](./built-in-directives.md) | The shared directive mechanism (restrict / priority / compile / link / scope kinds) plus per-category sub-sections: structural, visibility & binding, class & style, attribute helpers, events, pluralization, CSP/template-cache/element overrides |
| [Routing ($location + ngRoute)](./routing.md) | `$location` digest sync + cancelable `$locationChange*` events, the `$route` navigation pipeline (match → redirect → Start → template/resolve → commit), `$routeParams`, `ngView` render |

## Maintenance

Expand Down
141 changes: 141 additions & 0 deletions context/diagrams/routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Routing ($location + ngRoute)

## Purpose

`$location` keeps the browser address bar and the app's `/path?search#hash` URL in
sync through the digest, with a cancelable `$locationChangeStart` /
`$locationChangeSuccess` event pair. The opt-in `ngRoute` module builds on that:
`$routeProvider.when()` maps URL patterns to route definitions at config time,
`$route` runs the navigation pipeline on every location change (match → events →
template/resolve → commit), `$routeParams` exposes the live URL values, and the
`ngView` directive renders the active route's template with its per-route
controller.

## Collaborators & call order

```text
$location.path('/users/42') user clicks back/forward
│ (setter only marks state dirty) │ hashchange / popstate seam
▼ ▼
┌──────────────────────────────┐ ┌───────────────────────────────┐
│ $rootScope.$watch (per digest│ │ $$onUrlChange → guarded $apply │
│ flush; $LocationProvider.$get│ │ (throws → $exceptionHandler │
│ — initial pass fires with │ │ cause 'eventListener') │
│ newUrl === oldUrl) │ └───────────────┬───────────────┘
└──────────────┬───────────────┘ │
▼ ▼
$broadcast('$locationChangeStart', new, old, …) ── CANCELABLE
│ preventDefault()? ──▶ $$revert() (browser-driven veto
│ also force-REPLACEs the old URL)
▼ not vetoed
$$writeToBrowser() (push / replace per replace()) + $$commit()
│
▼
$broadcast('$locationChangeSuccess', new, old, …)
│
▼
┌────────────────────────────────────────────────────────────────────┐
│ $route — $on('$locationChangeSuccess') → update() │
│ 1. update-only? (same $$route + reloadOnUrl/reloadOnSearch off) │
│ └──▶ $broadcast('$routeUpdate') — no teardown, STOP │
│ 2. match $location.path() vs table (registration order, │
│ first match wins; fallback: otherwise entry) │
│ 3. redirectTo? ──▶ $location.path/url(...).replace() — NO route │
│ events; >10 consecutive hops ──▶ '$routeChangeError' │
│ (RouteRedirectionLoopError), STOP; a THROWING redirectTo │
│ fn ──▶ '$routeChangeError'(next, previous, err), STOP │
│ 4. $broadcast('$routeChangeStart', next, previous) — CANCELABLE │
│ 5. template + resolve: │
│ sync fast path (inline template, no resolve) → commit NOW │
│ async path: $q.all({ ...resolve, $template }) where │
│ resolve: string ──▶ $injector.get / fn ──▶ $injector.invoke│
│ $template: $q.when($templateRequest(url)) — cache-first │
│ stale navigation token? → drop silently │
│ rejection ──▶ $broadcast('$routeChangeError'), NO commit │
│ 6. commit: $route.current = next; repopulate $routeParams IN │
│ PLACE; $broadcast('$routeChangeSuccess', current, previous) │
└───────────────────────────────┬────────────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────────────┐
│ ngView — $on('$routeChangeSuccess') (+ once at link time) │
│ teardown old clone (scope.$destroy() BEFORE DOM removal) │
│ parse locals.$template → wrapper <div> ──▶ $compile(container) │
│ $controller(route.controller, { ...locals, $scope: newScope }) │
│ insert after Comment placeholder → linker(newScope) │
│ newScope.$emit('$viewContentLoaded') │
│ render-body throw ──route '$compile'──▶ $exceptionHandler │
└────────────────────────────────────────────────────────────────────┘
```

Collaborators: **`$rootScope`** (the digest-flush watch, the event bus, and
`$evalAsync` for `reload()`), **`$q`** + **`$templateRequest`** (the async
template/resolve aggregation that commits inside a digest turn), **`$injector`**
(resolve-map entries), **`$compile`** / **`$controller`** (ngView's render), and
**`$exceptionHandler`** (browser-event throws via `'eventListener'`, ngView render
throws via `'$compile'`). Route failures are delivered through the
`$routeChangeError` broadcast — the event IS the channel; `EXCEPTION_HANDLER_CAUSES`
stays at 13.

## Using it the primary way

The ESM-first API: `createLocation` / `createRoute` are pure factories with
injectable seams — usable (and testable) without an injector or a real browser.

```typescript
import { createLocation } from 'my-own-angularjs/location';

const fakeLocation = { href: 'http://host/#!/users/42?q=1', hash: '#!/users/42?q=1' };
const $location = createLocation({ locationRef: fakeLocation });

$location.path(); // '/users/42'
$location.search(); // { q: '1' }
$location.path('/next'); // standalone mode writes to the seam synchronously
fakeLocation.hash; // '#!/next?q=1'
```

```typescript
import { createRoute } from 'my-own-angularjs/route';

// All collaborators are injected seams — a scope, a $location, a $q, a
// $templateRequest, an $injector, and the compiled route table.
const $route = createRoute({ rootScope, location, routeParams, routes, q, templateRequest, injector });
$route.reload(); // schedules a forced full rebuild on the next tick
```

## Using it the dependency-injection way

`$location` is registered on the core `ng` module (lazy — no watch or browser
listeners until first injected). `ngRoute` is OPT-IN: compose it alongside the core
and declare `'ngRoute'` in the app's deps chain.

```typescript
import { createInjector, createModule } from 'my-own-angularjs';
import { ngModule } from 'my-own-angularjs/core';
import { ngRoute } from 'my-own-angularjs/route';

const app = createModule('app', ['ng', 'ngRoute']).config([
'$routeProvider',
($routeProvider) => {
$routeProvider.when('/users/:id', { template: '<h1>User {{id}}</h1>' }).otherwise({ redirectTo: '/users/1' });
},
]);

const injector = createInjector([ngModule, ngRoute, app]);
const $location = injector.get('$location');
const $route = injector.get('$route');
const $rootScope = injector.get('$rootScope');

$location.path('/users/42');
$rootScope.$digest(); // location flush → $routeChangeSuccess → ngView renders
```

`$locationProvider.hashPrefix('')` / `.html5Mode(true)` configure the URL style in
a `config()` block; `<ng-view></ng-view>` in compiled markup is the render slot.

## Related diagrams

- [Scopes & digest cycle](./scope-and-digest.md) — the digest that flushes `$location` writes and delivers the routing events
- [Template loading](./template-loading.md) — the cache-first `$templateRequest` path `templateUrl` routes ride
- [DOM compiler ($compile)](./compile.md) — compiles and links the route template `ngView` renders
- [Controllers](./controller.md) — instantiates the per-route controller with the resolve locals
- [Diagram index](./README.md)
8 changes: 4 additions & 4 deletions context/product/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ _High-level services that enable real application development._

_Features that complete the full framework experience._

- [ ] **Routing**
- [ ] **$routeProvider:** Implement route configuration with `when`, `otherwise`, and parameterized URL patterns.
- [ ] **ng-view:** Implement the view directive that renders route templates.
- [ ] **Route Lifecycle:** Support `resolve`, route change events (`$routeChangeStart`, `$routeChangeSuccess`, `$routeChangeError`), and `$routeParams`.
- [x] **Routing** _(spec 040 — shipped.)_
- [x] **$routeProvider:** Implement route configuration with `when`, `otherwise`, and parameterized URL patterns. _(spec 040 — the opt-in `ngRoute` module (the `ngSanitize` precedent), plus the `$location` service on core `ng` it builds on: hashbang default + HTML5 mode, digest-synced with the cancelable `$locationChangeStart`/`$locationChangeSuccess` pair. Patterns support `:name`/`:name?`/`:name*` + `caseInsensitiveMatch`; trailing-slash tolerance is folded into the compiled regexp instead of upstream's companion redirect route.)_
- [x] **ng-view:** Implement the view directive that renders route templates. _(spec 040 — `restrict: 'ECA'`, `transclude: 'element'`; renders `locals.$template` into a wrapper `<div>` against a fresh child scope with the per-route controller (resolve locals injectable by name, controller stashed for `require: '^ngController'`); emits `$viewContentLoaded`; render throws route via the existing `'$compile'` cause.)_
- [x] **Route Lifecycle:** Support `resolve`, route change events (`$routeChangeStart`, `$routeChangeSuccess`, `$routeChangeError`), and `$routeParams`. _(spec 040 — cancelable `$routeChangeStart`; `resolve` + template aggregated through one object-keyed `$q.all` with a staleness token (inline-template/no-resolve routes commit synchronously — a documented divergence); `$routeParams` repopulated in place; plus `redirectTo` (event-silent, `.replace()`, hardened with a 10-hop loop guard upstream lacks), `reloadOnSearch`/`reloadOnUrl` + `$routeUpdate`, `reload()`, and `updateParams()`.)_

- [ ] **Animations**
- [ ] **$animate Service:** Implement animation hooks for `enter`, `leave`, `move`, `addClass`, `removeClass`.
Expand Down
Loading