AngularJS (1.x) — Legacy
In short: AngularJS (1.x, 2010) is a JavaScript MVC/MVVM framework using controllers, $scope, two-way binding through dirty-checking digest cycles and directives;
- it reached end of life on 31 Dec 2021.
- Angular (2+, 2016) is a complete rewrite in TypeScript with components, a hierarchical DI system, RxJS, a CLI, AOT compilation, zone.js/signals-based change detection, mobile-friendly performance and regular releases.
- There's no direct upgrade path; migration is a rewrite (often incremental with ngUpgrade).
In short: Synchronisation between the model and the view in both directions: ng-model updates the scope when the user changes input, and any change to the scope model updates the view during the next digest cycle.
- It's implemented with watchers and dirty checking.
- Convenient, but many two-way bindings slow down large apps.
In short: Directives are markers on DOM elements (attributes, elements, classes, comments) that tell the AngularJS compiler to attach behaviour or transform the DOM.
- Built-in:
ng-app,ng-controller,ng-model,ng-bind,ng-repeat,ng-if,ng-show/ng-hide,ng-click,ng-class,ng-include,ng-switch,ng-options,ng-submit. - You can write custom directives and components.
In short: $scope is the object that glues a controller to its view — properties and functions on it are available in the template.
- Scopes form a hierarchy mirroring the DOM, with prototypal inheritance (child scopes from
ng-repeat,ng-if,ng-include). $rootScopeis the top-level scope of the app; every other scope descends from it.- Avoid storing state on
$rootScope— use services.
In short: ng-if adds or removes the element (and creates/destroys its child scope and watchers) based on the condition.
ng-show/ng-hidekeep the element in the DOM and toggledisplay: nonevia a CSS class, so its watchers remain active.- Use
ng-iffor heavy content that's rarely shown,ng-showfor frequent toggling of light content.
In short: All produce injectable singletons.
- A factory is a function whose returned value is the service.
- A service is a constructor function instantiated with
new(methods onthis). - A provider is the most configurable: an object with a
$getmethod, plus configuration methods available inconfig()blocks before the app runs (factory and service are shorthands built on providers). - Constants are injectable in config; values are not.
In short: When something inside AngularJS's context happens (ng-click, $http, $timeout), AngularJS calls $apply → $digest, which dirty-checks all watchers, runs listeners for changed values, and repeats until nothing changes (max 10 loops), then the view reflects the model.
- If you change the model outside AngularJS (native
setTimeout, jQuery plugin callbacks, WebSocket events), AngularJS doesn't know, so wrap the change in$scope.$apply()(or use$timeout/$applyAsync).
In short: The $injector looks at a function's parameter names (or explicit annotations) and supplies registered services.
- Minification renames parameters (
$http→a), so implicit DI breaks. - Fix with inline array annotation
["$http", function ($http) {}], the$injectproperty, build tools like ng-annotate, and enableng-strict-dito catch missing annotations.
In short: @ — one-way string/interpolated attribute value.
=— two-way binding (expensive, avoid in components).<— one-way binding of an expression (1.5+, preferred for inputs).&— callback expression bound to the parent's function, used for outputs (on-select="vm.open(id)"invoked as$ctrl.onSelect({ id: 5 })).
In short: Both dispatch scope events.
$emitsends an event upwards from the current scope through its parents to$rootScope.$broadcastsends it downwards to all child scopes.- Listen with
$on. - Overusing events makes data flow hard to follow — prefer services or component bindings; always deregister
$rootScope.$onlisteners on$destroy.
In short: Filters format data in templates (currency, date, uppercase, limitTo, orderBy, filter, json, number) and can be chained with |.
- Create one with
module.filter("name", () => input => transformed). - Filters in templates execute on every digest, so keep them cheap.
Reduce the number of watchers (one-time bindings ::, fewer two-way bindings, avoid deep $watch), use track by in ng-repeat, paginate or virtualise large lists, prefer ng-if for hidden heavy sections, move filters out of templates, debounce ng-model (ng-model-options="{ debounce: 300 }"), use $applyAsync and $httpProvider.useApplyAsync, disable debug info in production, and profile digest time with DevTools.
In short: Prepare the codebase (latest 1.8, components with one-way bindings, controller-as, services instead of $rootScope, TypeScript, module bundler).
- Then create a hybrid app with
@angular/upgrade(UpgradeModule): Angular bootstraps and boots AngularJS; - migrate leaf components and services first, downgrading Angular components for use in AngularJS templates and upgrading AngularJS services for Angular.
- Move routes one by one (or split by routes behind a proxy), test continuously, and finally remove AngularJS.
- Budget for the fact that AngularJS is EOL and has no security patches.
In short: The callback runs outside AngularJS's digest, so no dirty check happens.
- Wrap the update in
$scope.$apply(() => vm.date = value)(or$scope.$applyAsync), or better, wrap the datepicker in a custom directive that usesngModel.$setViewValueinside$apply. - Avoid calling
$applywhen a digest is already in progress ($digest already in progresserror) —$applyAsyncor$timeoutavoids that.
In short: Each keystroke triggers a digest over thousands of watchers plus filter re-evaluation.
- Fixes: debounce the input with
ng-model-options="{ debounce: 300 }"; - filter in the controller once per change instead of a template filter;
- add
track by row.id; - use one-time bindings (
::) for row fields that don't change; - paginate or virtualise (e.g.
ui-gridor a virtual-repeat directive); - reduce per-row bindings and directives.
In short: Risks: no security patches since Dec 2021 (compliance/pen-test findings), dwindling developer pool, incompatible modern libraries, browser changes.
- Plan: short-term — upgrade to 1.8.3, add a strict Content-Security-Policy, review
$sce/ng-bind-htmlusage, consider commercial extended support; - medium-term — incremental migration (Angular via ngUpgrade, or React/Vue via micro-frontends/route-by-route strangler), starting with the most-changed or riskiest screens;
- keep feature parity tests (Playwright) as a safety net.