bhylu.com

Advertisements

AngularJS Component Lifecycle

Component hooks describe distinct moments in a component’s lifetime. Use $onInit after initial bindings, $onChanges for binding changes and $onDestroy for owned resource cleanup.

How it works

The removable card sets its initial message during $onInit and records destruction through a shared local log. The parent can remove it with ng-if. This makes creation and teardown observable without guessing that a hidden element must have been destroyed. A real hook should release its actual subscriptions or listeners, not merely record a message.

Run the code

AngularJS 1.8.3 is a legacy framework whose official support ended in January 2022. These examples teach maintenance, not a recommendation for a new application. They need a separate local browser page; Bhylu has no AngularJS playground.

In an empty folder save the HTML below as component-lifecycle.html and the server as lab-server.mjs. Make a vendor folder and save the four pinned libraries there, retaining their MIT notices. With Node 22 or later run node lab-server.mjs component-lifecycle.html, then open http://127.0.0.1:8136/. Stop that lab before running a different lesson. The server supplies fixed read-only HTTP fixtures; no data is saved and it does not connect to SQL. No PHP, Apache or MySQL configuration changes are needed.

Example

component-lifecycle.html

<!doctype html>
<html lang="en" ng-app="repairLab" ng-strict-di>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>AngularJS Component Lifecycle</title>
    <style>
      :root {
        font:
          18px/1.55 system-ui,
          sans-serif;
        color: #0f3464;
        background: #fff;
        color-scheme: light;
      }
      * {
        box-sizing: border-box;
      }
      body {
        margin: 0;
      }
      main {
        max-width: 50rem;
        margin: auto;
        padding: 1.2rem;
      }
      h1 {
        font-size: 1.65rem;
      }
      label {
        display: block;
        margin: 0.6rem 0;
      }
      input,
      select,
      textarea,
      button {
        font: inherit;
        max-width: 100%;
        padding: 0.35rem;
        margin: 0.2rem;
      }
      button {
        border: 1px solid #034fa0;
        border-radius: 0.25rem;
        background: #034fa0;
        color: white;
      }
      button:disabled {
        opacity: 0.55;
      }
      output,
      [role="status"] {
        display: block;
        min-height: 1.5rem;
      }
      table {
        border-collapse: collapse;
        width: 100%;
      }
      td,
      th {
        padding: 0.4rem;
        border: 1px solid #a5bbd4;
        text-align: left;
      }
      pre {
        white-space: pre-wrap;
        overflow-wrap: anywhere;
      }
      :focus-visible {
        outline: 3px solid #0078d4;
        outline-offset: 3px;
      }
      [ng-cloak] {
        display: none !important;
      }
      .marked {
        font-weight: 700;
      }
      .error {
        color: #a02424;
      }
      :root[data-theme="dark"] {
        color: #d6e7fb;
        background: #061d30;
        color-scheme: dark;
      }
      :root[data-theme="dark"] .error {
        color: #ffb4b4;
      }
    </style>
  </head>
  <body>
    <main ng-controller="DeskController as vm" ng-cloak>
      <h1>Community repair desk</h1>
      <p>AngularJS 1.8.3 · Legacy maintenance lab · Local demonstration</p>
      <button type="button" ng-click="vm.show=false">Remove card</button
      ><repair-life ng-if="vm.show"></repair-life>
      <p data-result>{{ vm.log.destroyed }} destroyed</p>
    </main>
    <script src="/vendor/angular.min.js"></script>
    <script>
      (function () {
        "use strict";
        var app = angular.module("repairLab", []);
        app.factory("lifeLog", [
          function () {
            return { destroyed: 0 };
          },
        ]);
        app.component("repairLife", {
          template: "<span data-life>{{ $ctrl.message }}</span>",
          controller: [
            "lifeLog",
            function (log) {
              this.$onInit = function () {
                this.message = "Card initialized";
              };
              this.$onDestroy = function () {
                log.destroyed += 1;
              };
            },
          ],
        });
        app.controller("DeskController", [
          "lifeLog",
          function (log) {
            this.show = true;
            this.log = log;
          },
        ]);
      })();
    </script>
  </body>
</html>

Focus on the bindings and controller

This excerpt leaves out the document shell, styles and library files. Use the complete download to run it.


<button type="button" ng-click="vm.show=false">Remove card</button
><repair-life ng-if="vm.show"></repair-life>
<p data-result>{{ vm.log.destroyed }} destroyed</p>
<script>
  (function () {
    "use strict";
    var app = angular.module("repairLab", []);
    app.factory("lifeLog", [
      function () {
        return { destroyed: 0 };
      },
    ]);
    app.component("repairLife", {
      template: "<span data-life>{{ $ctrl.message }}</span>",
      controller: [
        "lifeLog",
        function (log) {
          this.$onInit = function () {
            this.message = "Card initialized";
          };
          this.$onDestroy = function () {
            log.destroyed += 1;
          };
        },
      ],
    });
    app.controller("DeskController", [
      "lifeLog",
      function (log) {
        this.show = true;
        this.log = log;
      },
    ]);
  })();
</script>

Expected result

0 destroyed

The initial card displays its initialization message. Removing it changes the count to one, and another click does not destroy it twice. The browser test checks the initial hook and the teardown result. This is a view-lifetime check, not a promise that every possible resource is automatically cleaned up.

Advertisements