llselect + AngularJS 1.x - examples

Example directives, not a supported wrapper. Copy llselect-angularjs.js (or llselect-ui-select.js) into your project and adapt it. The reasoning, the source references, and the list of deliberate gaps are in angularjs/README.md.

Run npm run build, then npm run serve:site, and open /demo/angularjs/examples.html. AngularJS itself comes from a CDN; nothing AngularJS-related is in the package.

1. Basic: <llselect-single>

ll-options takes an ng-options expression. Every clause maps onto an llselect setting; see the table in the README.

ng-model = {{ vm.country || 'undefined' }}
markup

2. Basic: <llselect-multiple> - count / tags / choose-all

The trigger has two displays: the default 'count' summary and ll-trigger-display="'tags'" chips. Rows carry live checkbox icons by default (ll-checkboxes="false" opts out), and ll-choose-all-row="true" adds the tri-state row acting on the visible enabled subset.

2a. Default trigger: count summary

ng-model = {{ vm.langsCount | json }}
markup

2b. Tags trigger

ng-model = {{ vm.langs | json }}
markup

2c. Choose-all row

ll-checkboxes (default true) draws SVG checkboxes on the rows and this row; ll-checkboxes="false" strips them all, leaving plain text - which is also the core's own default (core demo 5.7).

ng-model = {{ vm.langsAll | json }}
markup

2d. Hide chosen rows: ll-hide-chosen-rows

Chosen items leave the popup; removing a tag puts them back, and the clear button puts everything back at once. Pairs with the tags trigger, and with ll-checkboxes="false" - a listed row here is always unchecked, so checkboxes would say nothing.

ng-model = {{ vm.langsHidden | json }}
markup

3. Form validation: name + required + form.$valid

The point of this page. myForm.fruit resolves because AngularJS reads name off this element as a plain attribute - there is no native <select> and no hidden input anywhere. Clear the selection to watch the form go invalid.

myForm.$valid = {{ myForm.$valid }} | myForm.$dirty = {{ myForm.$dirty }}
myForm.fruit.$error = {{ myForm.fruit.$error | json }}
myForm.toppings.$error = {{ myForm.toppings.$error | json }} (toppings shows the $isEmpty override: [] counts as empty)
$dirty must stay {{ myForm.$dirty }} - a data reload is not a user edit.
markup

4. Ids in ng-model: select as

select as is the ngModel value projection. llselect has no equivalent setting on purpose - the app declares it, exactly as ng-options does.

ng-model = {{ vm.userId }} (a number, not the object)
markup

5. ll-disabled

Setting attributes are read once at construction; ll-disabled gets a $watch because it maps onto the setDisabled() method.

markup

6. ll-arrow, and llselectConfigProvider defaults

ll-arrow takes llselect's two built-in arrows and nothing else; a custom one means editing your copy of the file. The house style comes from llselectConfigProvider in .config() (see app.js) - this demo sets arrow: 'chevron' app-wide, which is why the selects above have one without saying so. A per-element ll-arrow wins over it.

markup

7. Custom item HTML: ll-item-content-fn

An expression evaluating to a function (item) => HTMLElement | null, passed straight to llselect's createItemContentElFn. It runs per rendered row per render, outside any digest, and the element is never $compiled - custom rows with zero per-row scope or watcher. The accessible name and filter text stay the item text from ll-options (icons and hints never reach a screen reader). All renderers are in app.js. For real per-row Angular templates, that is <ui-llselect> below.

7a. Icon rows, mirrored into the trigger

renderLangRow builds the row with createElement; feeding the SAME renderer to ll-trigger-content-fn is what makes the trigger show the chosen row - there is no auto-projection.

ng-model = {{ vm.lang || 'undefined' }}
markup

7b. Template-clone flavor, beside the checkboxes

renderLangRowFromTpl clones the <template> and fills it - the row markup lives in HTML, still with no scope and no $compile. On a multiple the element renders beside the default checkbox.

ng-model = {{ vm.langsRich | json }}
markup

7c. Faded hint text

renderUserRow: primary text plus a faded hint pushed to the row's right edge (.user-row), with disable when dimming suspended users on top. The name highlights the filter match via the core createHighlightedTextEl (7f's pattern).

ng-model = {{ vm.user2 || 'undefined' }}
markup

7d. Rich tag chips

ONE renderer feeds rows AND chips (ll-tag-content-fn); ll-tag-remove-button-content-fn swaps the remove icon, else the theme's CSS glyph draws the x.

ng-model = {{ vm.langsTags | json }}
markup

7e. Per-item background tint

What <option style="background-color"> does on a native select (Chromium only): renderTintedLangRow is 7a's row tinted from its icon color, with createCheckmarkSvgEl on the chosen row (rows re-render on chosen changes - never stale). All inline styles, plus one demo CSS line (lang-tinted) zeroing the option element's padding. The whole trigger takes the chosen colors: demo-tint-trigger requires the published controller and paints instance().triggerEl (API.md "Reaching the instance from your own directive").

ng-model = {{ vm.langTinted || 'undefined' }}
markup

7f. Highlight the filter matches: ll-highlight

ll-highlight="true" wraps each query match in the default item text in <mark> (the core createHighlightedTextEl); the marks follow the typing. With a custom ll-item-content-fn call the helper yourself, the way 7c does.

ng-model = {{ vm.hlCountry || 'undefined' }}
markup

8. <ui-llselect>: the ui-select-compatible directive

A separate directive (llselect-ui-select.js, module llselect.uiCompat) that takes ui-select's call-site markup, so an existing ui-select codebase can migrate without rewriting every call site. New code should use <llselect-single> / <llselect-multiple> above instead.

Migrating a call site: rename the elements (ui-select family to ui-llselect family) and add ll-item-text - ui-select has no item-to-string concept, and llselect needs one for the option's accessible name. The template content carries over untouched. CSS does not - llselect owns the DOM, so this is styled by the llselect theme.

8a. Rich row template (highlight + ui-disable-choice)

{{$select.selected.name}} {{p.role}}{{p.suspended ? ' - suspended' : ''}}
ng-model = {{ vm.person | json }}
markup

8b. Multiple: chips from the match template

As in real ui-select, a chosen person leaves the dropdown: remove-selected defaults to true (8g shows the opt-out).

{{$item.name}}
ng-model = {{ vm.people.length }} selected
markup

8c. allow-clear

{{$select.selected.name}}
ng-model = {{ vm.person2.name || 'undefined' }} (ui-select's own allow-clear carries over: the x clears back to undefined)
markup

8d. Template shapes: bare text / none

The template has three shapes: elements (above), a bare text node, or none at all - rows then render the ll-item-text string itself, which ui-select cannot do (its template is its only source of item text).

{{$select.selected.name}} {{p.name}} ({{p.role}})
ng-model = {{ vm.person3.name || 'undefined' }} / {{ vm.person4.name || 'undefined' }}
markup

8e. Custom filter, ui-select style

The | filter: chain IS the custom filter, re-evaluated once per typed query. Try vlan2 - the ifaceMatch filter (app.js) ignores spaces.

{{vm.ifaceText($select.selected)}}
ng-model = {{ vm.iface2 | json }}
markup

8f. ng-disabled + uib-tooltip (angular-ui-bootstrap)

Interop with uib-tooltip from angular-ui-bootstrap - the component kit most AngularJS apps already carry: both directives sit on the same element, no conflict. ng-disabled carries over here (ui-select parity, via attrs.$observe; the main directives use ll-disabled). The attribute is inert on this host, so hover works in BOTH states - the tooltip keeps working while disabled, which a native disabled control forbids.

{{$select.selected.name}}
ng-model = {{ vm.person5.name || 'undefined' }}
markup

8g. remove-selected: hide or keep chosen rows

Top: the default. A chosen person leaves the list, exactly like ui-select (remove-selected defaults to true; bridged onto hideChosenRows). Its allow-clear puts everyone back at once - the deviation the API page documents. Bottom: remove-selected="false" keeps chosen rows listed.

{{$item.name}} {{p.name}}
{{$item.name}} {{p.name}}
ng-model = {{ vm.peopleHide.length }} / {{ vm.peopleKeep.length }} selected
markup

Not bridged, by rule (llselect has no such concept, so it is ignored rather than half-implemented): tagging, sortable, append-to-body, async refresh, limit, themes.

9. ghiscoding/angular-validation

A third-party validation library, included because it is the case that started this: it throws unless the field has a name (validation-common.js:460). It reads that name off the ng-model element like everything else in AngularJS, so it needs no native <select> either. Its validation directive is restrict: 'A', require: 'ngModel', and it revalidates from a watch on ctrl.$modelValue - all element-agnostic.

avForm.$valid = {{ avForm.$valid }} | avForm.avFruit.$error = {{ avForm.avFruit.$error | json }}

Three integration facts worth knowing: it hard-depends on pascalprecht.translate; its messages need $translateProvider pointed at its locale files or you get "Could not translate: 'INVALID_REQUIRED'"; and validation is debounced by typingLimit (default 1000 ms), so it is not instant. Its elm.bind('blur') trigger is inert here - blur does not bubble and the focusable element is llselect's inner trigger - but the $modelValue watch is the path that matters for a select.

markup

10. Label element: ll-label-el

Native <label for> cannot target these divs. ll-label-el points at your label's id: the label names the field, and clicking it focuses the trigger - focus only, like a native label. (Core demo 12, as an attribute.)

ng-model = {{ vm.labelFruit || 'undefined' }}
markup

11. Custom filter: ll-filter-fn

Items are {interfaceType: 'vlan', interfaceNo: '2'}, shown as VLAN 2. The default filter matches the shown text (vlan 2 works out of the box); the custom fn also matches the space-less vlan2.

ng-model = {{ vm.iface | json }}
markup

12. Full source

The whole of what this page loads. Nothing is generated and nothing is minified: this is the file you copy.

llselect-angularjs.js - <llselect-single> / <llselect-multiple>
ui-llselect.js - the ui-select-shaped bridge
app.js - this demo's own controller