llselect + AngularJS 1.x

npm version

AngularJS 1.x directives for llselect, published as @llselect/angularjs. Two independent files: pick the one you need, or use both while migrating.

<script src="node_modules/@llselect/core/dist/index.umd.js"></script>
<script src="node_modules/@llselect/angularjs/llselect-angularjs.min.js"></script>
<!-- or, from a CDN, or just dropped in vendor/ -->
<script src="https://cdn.jsdelivr.net/npm/@llselect/angularjs@0/llselect-angularjs.min.js"></script>
angular.module('app', ['llselect'])
<llselect-single name="fruit" ng-model="picked" required ll-filterable="true"
  ll-options="f.id as f.name group by f.type disable when f.soldOut for f in fruits track by f.id">
</llselect-single>

Both files are plain IIFEs reading window.angular and window.llselect - the same shape angular.js itself, ui-select and angular-validation all ship - so there is nothing to bundle or transpile, and a <script src> in <head> is a supported way to use this, not a fallback. It is not a generic wrapper: it handles what is documented here and reports the rest.

Every claim below about AngularJS internals was verified against the AngularJS 1.8.3 source, and every claim about ui-select against ui-select 0.19.8. File:line references point at those versions.

Deeper material lives beside this file, split by who reads it: API.md is the complete attribute reference, one entry per attribute; DESIGN.md is why this package exists at all and why each piece is shaped the way it is; SPEC.md is the source-level evidence behind every claim made here, with file:line references into AngularJS and ui-select. If you only want to use the directives, this file plus API.md is enough.

Why this exists when React and Vue do not

llselect ships no framework wrappers. All three reasons for that fail for AngularJS specifically:

React and Vue fail the last two (no standard expression grammar, no standard state layer), so the boundary holds for them. This is not an exception carved out of the rule; the rule's premises just do not apply here. The full reasoning and the rejected alternatives: DESIGN.md.

What this is not

Files

file module what
llselect-angularjs.js llselect <llselect-single> / <llselect-multiple>, driven by an ng-options-style ll-options expression. Start here.
llselect-ui-select.js llselect.uiCompat <ui-llselect>, which accepts ui-select's call-site markup. For migrating an existing ui-select codebase; needs llselect-angularjs.js loaded too.

Each ships a .min.js beside it (built by npm run build, terser only - there is no bundler). The live examples and benchmark load these files directly, so what the demo shows is what the package ships.

The two name attributes

The single most useful thing to know here, because it is a name collision that makes an entire class of workaround look necessary when it is not.

AngularJS's name and HTML's name are unrelated mechanisms that happen to share a spelling. On a native element both readers read the same attribute at once, which is why they look like one thing:

<select name="fruit" ng-model="x">
<!--          ^ AngularJS reads this ($attr.name -> myForm.fruit)
              ^ the browser also reads this (the HTTP serialization key) -->

AngularJS reads it as a plain attribute off whatever element ng-model sits on, with no element-type check anywhere - and the same goes for required and for form.$valid. So they diverge the moment the element is not a native form control:

markup AngularJS myForm.fruit browser HTTP submit
<select name="fruit" ng-model="x"> yes yes, it is a form-associated element
<llselect-single name="fruit" ng-model="x"> yes no, not form-associated; the browser ignores the attribute
<llselect-single name="fruit" ng-model="x"> + a hidden input yes (on the custom element) yes (on the hidden input)

Row 2 is what these directives give you:

<form name="myForm" novalidate>
  <llselect-single name="fruit" ng-model="picked" required
    ll-options="f for f in fruits"></llselect-single>
</form>
<!-- myForm.$valid, myForm.fruit.$error.required, myForm.$dirty all work.
     No native <select>. No hidden input. Nothing generated. -->

The value is not POSTed by a plain form submit. If you need that too, the two name attributes live on two different elements and do not interfere: put a hidden input beside it and mirror the value in onChange. See "No native form integration" in DESIGN.md for why the package does not do that for you, and SPEC.md for the source references behind every claim above.

The name is always written by you, in your own template - the package never generates one. A generated name (say, an auto-incrementing counter) would key your server contract to JavaScript execution order: reorder your code, or mount two widgets in a different order, and the payload keys silently swap.

Gotchas

All verified against AngularJS 1.8.3 source, not folk wisdom.

ghiscoding/angular-validation

Works on these directives, verified: <llselect-single name="fruit" ng-model="x" validation="required"> drives form.$valid with no native <select> anywhere. It is the case that motivates the whole "two names" section, because it is the one thing in this stack that genuinely requires a name - validation-common.js:460 throws without one:

throw 'Angular-Validation Service requires you to have a (name="") attribute on the element to validate...'

But it reads that name the same way everything else in AngularJS does, off the ng-model element. Its validation directive is restrict: 'A', require: 'ngModel', and it revalidates from a $watch on ctrl.$modelValue (validation-directive.js:356) - all element-agnostic.

Four things to know when wiring it up:

The ui-select bridge

<ui-llselect> (llselect-ui-select.js) exists so an existing ui-select codebase can migrate without rewriting every call site. The scoping rule is: bridge what llselect has; ignore what it does not. Nothing is half-implemented to look compatible. What carries over, the one added attribute (ll-item-text) and the deliberate deviations are in API.md.

Implementation notes

Four protected methods plus close() are overridden by subclassing, which DESIGN.md ("Customization model") names as the sanctioned path for a framework wrapper:

The bridge hangs off a WeakMap rather than an instance field because it cannot exist before super() runs.

Why it is not a full ui-select reimplementation

Short version: honoring ui-select's row-scope contract would mean the rows come from its ng-repeat and theme template, which is exactly the work llselect's own rendering does - so a faithful reimplementation would use approximately none of llselect. The long version, with the source references, is in DESIGN.md and SPEC.md.