Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

AngularJS (1.x) is best understood as MVC/MVVM-like: templates render scope-backed state, controllers expose view behavior, and services hold reusable logic, while directives and the digest system connect the pieces. Official AngularJS support ended in January 2022, so this guide is for developers maintaining or migrating existing applications—not a recommendation for a new project. The AngularJS project directs developers to actively supported Angular. Official support status.

What MVC and MVVM mean in AngularJS

AngularJS does not fit one uncontested, textbook MVC or MVVM definition. Its architecture combines declarative HTML templates, scopes, controllers, directives, dependency injection, and automatic synchronization. Calling it MVC/MVVM-like is more useful than arguing over a single label: the pattern helps decide where state and behavior belong.

AngularJS concept Practical role
Templates and DOM The view: HTML, interpolation, and directive attributes.
Scope and application data Model-facing state exposed to expressions; scopes also provide the binding context.
Controller View-related behavior and commands made available to a template.
Scope and component controller A ViewModel-like layer mediating between template bindings and application state.
Services Reusable, view-independent business logic.
Directives, compiler, dependency injection, digest/watch Binding and orchestration: they connect markup, behavior, dependencies, and updates.

The official guide describes scope as “the glue between application controller and the view.” AngularJS scope guide.

Start with a template and two-way binding

AngularJS templates can bind an input to a model-facing property with ng-model, then display the same property with interpolation:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<div ng-app="demo" ng-controller="GreetingController">
  <label>Name: <input ng-model="name"></label>
  <p>Hello, {{ name }}!</p>
</div>

When a user changes the input, ng-model updates name; interpolation reflects the updated value in the view. When application code changes the bound value within AngularJS’s execution context, the view can reflect that change as well. This synchronization is commonly called two-way data binding. It is convenient, but it can obscure who owns a value when many scopes inherit or modify it.

Use controllers for view behavior and services for reusable logic

A controller exposes the behavior a particular view needs. Keep it focused on that view rather than DOM manipulation or reusable business rules. Services are the better home for logic shared across views because they are view-independent and can be injected where needed.

angular.module('demo', [])
  .service('greetingService', function() {
    this.messageFor = function(name) {
      return 'Hello, ' + name + '!';
    };
  })
  .controller('GreetingController', function($scope, greetingService) {
    $scope.name = 'Ada';
    $scope.greeting = function() {
      return greetingService.messageFor($scope.name);
    };
  });

A template can then render the controller’s behavior with {{ greeting() }}. The controller and directives can both reference scope without referencing each other; this separation keeps controllers more view-agnostic and makes their logic easier to test. AngularJS controller guide.

How scopes work—and why inheritance surprises legacy code

A scope is an execution context for expressions and a model-facing object. AngularJS arranges scopes in a hierarchy that mirrors the DOM. A child scope can inherit properties through JavaScript prototypical inheritance, so a property visible in a nested template may have been defined higher in the tree rather than locally. AngularJS scope guide.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Primitive shadowing

A frequent maintenance trap occurs when a child scope assigns to an inherited primitive. Instead of changing the parent’s property, the assignment can create a child property that shadows it. The parent and child then appear to disagree about the same name. For nested or component-bound state, use explicit bindings or an object whose property is being updated; do not rely on accidental inheritance to communicate ownership.

Make scope boundaries intentional

When a directive needs only a few values, pass those values explicitly rather than letting it read arbitrary properties from an inherited scope. This narrows coupling and makes the directive’s inputs easier to find, test, and change.

How $apply, $digest, and $watch synchronize updates

AngularJS tracks registered $watch expressions during a $digest cycle. Changes are propagated through $apply, which enters AngularJS’s update process and then runs a digest to check watched expressions. This is how changes made in AngularJS-managed event handling can update bound views. AngularJS scope guide.

The distinction matters when a callback originates outside AngularJS—for example, a native timer or a third-party library callback. If that code changes application state without entering AngularJS’s update context, the view may not update immediately. Where appropriate, enter the framework with $scope.$apply() around the external change, or use an AngularJS-managed mechanism such as $timeout. Avoid calling $apply while a digest is already in progress; doing so can raise an in-progress error. Prefer integrating external events at a clear boundary instead of scattering manual update calls throughout the application.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When to refactor a controller into a component

Controller-plus-scope code often accumulates inherited properties and view assumptions. A component creates a clearer boundary: its controller and template work with an isolate scope and explicitly declared bindings. Components created with .component() always create isolate scopes. AngularJS component guide.

Rank #4
AngularJS
  • Used Book in Good Condition
angular.module('demo', [])
  .component('greetingCard', {
    bindings: {
      name: '<',
      onChange: '&'
    },
    template: '<p>Hello, {{$ctrl.name}}!</p>'
  });

Here the component accepts a one-way input and an output callback. Explicit bindings make the relationship to the parent visible in the component declaration instead of depending on an implicit shared scope. They also make ownership and data flow easier to review when refactoring.

Controller-and-scope MVC style vs component-oriented structure

Concern Controller plus scope Component-oriented structure
State ownership May be shared through parent and child scope inheritance. Inputs and outputs are declared at the component boundary.
View coupling Can grow as controllers and templates rely on broad scope properties. Can be limited to a smaller component API.
Reuse Often relies on ad hoc scope properties. Isolated, parameterized components can be reused.
Testability View-agnostic controller and service logic are easier to test than DOM-heavy behavior. Component behavior has a defined boundary, though DOM behavior still needs appropriate tests.
Binding flow May depend on implicit inherited properties. Explicit inputs and outputs show the intended flow.
Migration effort Broad scope and directive coupling can make dependencies harder to untangle. Clearer boundaries help isolate code for incremental migration.

Use directives for focused DOM behavior

Directives let AngularJS extend HTML with behavior. Use a custom directive when the job is a narrowly defined interaction with the DOM or a reusable behavior—not as a catch-all for application logic. Give it only the models it needs through explicit scope bindings. AngularJS’s directive guide documents how directives can define these scope relationships. AngularJS directive guide.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Structure and test a legacy AngularJS application

For maintenance work, organize code around meaningful features and responsibilities rather than accumulating behavior in a single controller or broad shared scope. A practical division is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Templates and components: present a feature and declare the data and callbacks crossing its boundary.
  • Controllers: expose view-specific state and commands without manipulating the DOM directly.
  • Services: hold reusable, view-independent logic and data access that can be injected and tested separately.
  • Directives: encapsulate focused DOM behavior with explicit dependencies.
  • Modules: group application code and declare dependencies through AngularJS dependency injection.

Test services and controller behavior without involving the DOM when possible. Reserve DOM-oriented tests for directives and components whose purpose depends on rendering or interaction. When changing a legacy feature, trace where a value originates, which scope owns it, and which bindings or watches update it before changing its behavior.

Plan a migration without mistaking AngularJS for supported Angular

AngularJS and Angular are distinct frameworks, not interchangeable names for the same version line. AngularJS support officially ended in January 2022; the project directs users to actively supported Angular. Official AngularJS support status. For an existing system, reduce migration friction by replacing broad scope sharing with explicit component inputs and outputs, moving reusable logic into services, and isolating DOM work in directives. These boundaries make it easier to identify feature-sized units for eventual migration to supported Angular or another maintained stack.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.