AngularJS DOM and jqLite
AngularJS wraps elements with jqLite when full jQuery has not been loaded first. Its API is a limited subset, so familiar jQuery method names are not evidence that every jQuery operation is available.
How it works
The repair action directive owns a native click listener. It evaluates a trusted developer-authored callback through $parse and schedules model changes with $applyAsync. The link function also marks the element for an observable integration check. Its destroy listener removes the native handler, preserving a clear resource lifetime when a view disappears.
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 dom.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 dom.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
<!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 DOM and jqLite</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" repair-action="vm.record()">Record action</button>
<p data-result>{{ vm.count }} actions</p>
</main>
<script src="/vendor/angular.min.js"></script>
<script>
(function () {
"use strict";
var app = angular.module("repairLab", []);
app.directive("repairAction", [
"$parse",
function ($parse) {
return {
restrict: "A",
link: function (scope, element, attrs) {
var action = $parse(attrs.repairAction);
function clicked() {
scope.$applyAsync(function () {
action(scope);
element.attr("data-handled", "yes");
});
}
element.on("click", clicked);
scope.$on("$destroy", function () {
element.off("click", clicked);
});
},
};
},
]);
app.controller("DeskController", [
function () {
var vm = this;
vm.count = 0;
vm.record = function () {
vm.count += 1;
};
},
]);
})();
</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" repair-action="vm.record()">Record action</button>
<p data-result>{{ vm.count }} actions</p>
<script>
(function () {
"use strict";
var app = angular.module("repairLab", []);
app.directive("repairAction", [
"$parse",
function ($parse) {
return {
restrict: "A",
link: function (scope, element, attrs) {
var action = $parse(attrs.repairAction);
function clicked() {
scope.$applyAsync(function () {
action(scope);
element.attr("data-handled", "yes");
});
}
element.on("click", clicked);
scope.$on("$destroy", function () {
element.off("click", clicked);
});
},
};
},
]);
app.controller("DeskController", [
function () {
var vm = this;
vm.count = 0;
vm.record = function () {
vm.count += 1;
};
},
]);
})();
</script>
Expected result
0 actions
A click reaches the controller and the marker confirms the directive handled it. The review checks both. The expression comes from a controlled template; accepting a visitor-provided expression string would be a completely different and unsafe trust boundary.