Autocomplete suggestions not shown after all chips removed (md-chips + md-autocomplete) - angularjs

In my example (https://jsfiddle.net/vv18yjzo/7/) the autocomplete suggestions should be shown, as long as the md-chips input is focused. This works well when I enter a few items.
However, when all available items are added and then removed, the autocomplete suggestions are empty / hidden. I have to enter a new search text to see the autocomplete suggestions again.
HTML:
<md-input-container class="md-block">
<label>Categories</label>
<md-chips ng-model="vm.selectedCategories"
md-autocomplete-snap
md-require-match="true"
md-on-add="vm.onCategoryAdded($chip, $index)"
md-on-remove="vm.onCategoryRemoved($chip, $index)">
<md-autocomplete
md-selected-item="vm.autocomplete.selectedItem"
md-search-text="vm.autocomplete.searchText"
md-selected-item-change="vm.onSelectedItemChanged()"
md-search-text-change="vm.onSearchTextChanged()"
md-items="item in vm.searchCategories()"
md-item-text="item.name"
md-min-length="0"
md-select-on-match="true"
md-match-case-insensitive="true"
md-no-cache="true"
placeholder="Choose categories">
<span md-highlight-text="vm.autocomplete.searchText"
md-highlight-flags="i">{{item.name}}</span>
</md-autocomplete>
<md-chip-template>
<span>
<strong>{{$chip.name}}</strong>
</span>
</md-chip-template>
</md-chips>
</md-input-container>
JS:
(function () {
'use strict';
angular
.module('app', [
'ngMaterial',
'ngMessages'
])
.factory('CategoriesService', CategoriesService)
.controller('CategoriesCtrl', CategoriesCtrl);
function CategoriesService($q, $timeout) {
var that = {
find: find
},
categories = [
{ name: 'a' },
{ name: 'ab' },
{ name: 'abc' },
{ name: 'abcd' },
{ name: 'abcde' },
{ name: 'abcdef' }
];
function find(search, selectedCategories) {
var deferred = $q.defer(),
result = categories.filter(function(category) {
return (-1 === selectedCategories.indexOf(category) && -1 !== category.name.indexOf(search));
});
$timeout(function() {
deferred.resolve(result);
}, 100);
return deferred.promise;
}
return that;
}
function CategoriesCtrl($scope, CategoriesService) {
var vm = this;
vm.selectedCategories = [];
vm.autocomplete = {
selectedItem: null,
searchText: null
};
/* ... */
vm.searchCategories = function() {
return CategoriesService.find(vm.autocomplete.searchText.toLowerCase(), vm.selectedCategories);
};
}
})();
Example: https://jsfiddle.net/vv18yjzo/7/

There seems to be a weird bug with the autocomplete searchtext, if you remove an item it just unsets the searchtext variable, which then breaks the autocomplete events.
Check this working fiddle https://jsfiddle.net/mgsLkf89/3/
vm.onCategoryRemoved = function($chip, $index) {
if (!vm.autocomplete.searchText)
vm.autocomplete.searchText=null;
};

Related

Pass parameters to a component from a parent controller

I am using Angularjs 1.7 and I am trying to pass an array from the parent controller to the component.How can I pass arraySteps and selectedSteps to the component.
I tried the following but didn't work. I am not getting the arraySteps in the component controller
ulMultiselect.component.js
angular.module('ulMultiselect.module').component('ulMultiselect', {
templateUrl: 'app/components/ulMultiselect/ulMultiselect.template.html',
controller: 'ulMultiselect.controller',
bindings: {
arraySteps: '<'
},
transclude: true
});
ulMultiselect.controller.js
angular.module('ulMultiselect.module').controller('ulMultiselect.controller',
function () {
var self = this;
self.$onInit = function () {
if (typeof self.arraySteps === 'undefined') {
self.arraySteps = [];
}
};
});
ulMultiselect.template.html
<div layout="row">
<md-input-container style="width:100%">
<label>Steps</label>
<md-select ng-model="$ctrl.selectedSteps" multiple>
<md-option ng-value="step.StepName" ng-repeat="step in $ctrl.arraySteps">{{step.StepName}}</md-option>
</md-select>
</md-input-container>
</div>
mainTest.html
<ul-Multiselect arraySteps="$ctrl.steps"></ul-Multiselect>
mainTest.controller.js
angular.module('view.mainTest.module').controller('view.mainTest.controller',
function () {
var self = this;
self.$onInit = function () {
self.steps = [{ StepName: "Step1" }, { StepName: "Step2" }, { StepName: "Step3" }];
};
});
The one-way binding needs to be normalized to kebab-case:
̶<̶u̶l̶-̶M̶u̶l̶t̶i̶s̶e̶l̶e̶c̶t̶ ̶a̶r̶r̶a̶y̶S̶t̶e̶p̶s̶=̶"̶$̶c̶t̶r̶l̶.̶s̶t̶e̶p̶s̶"̶>̶<̶/̶u̶l̶-̶M̶u̶l̶t̶i̶s̶e̶l̶e̶c̶t̶>̶
<ul-multiselect array-steps="$ctrl.steps"></ul-multiselect>
For more information, see
AngularJS Developer Guide - Directive Normalization

AngularJS - Watch filtered list for changes

Within angular I have a filtered list of people that takes the filter criteria from a predicate function. I want to watch a variable of the filtered list (called filteredPeople) every time the filtered list changes. But I am unable to see when that variable changes.
My code is below:
HTML:
<ul>
<li ng-repeat="person in ($ctrl.filteredPeople = ($ctrl.people | filter: $ctrl.filter))">
...
</li>
</ul>
JS:
controller: ['$scope',
function ($scope) {
var $ctrl = this;
$ctrl.people = {...}
$ctrl.filteredPeople = [];
$scope.$watch($ctrl.filteredPeople, function () {
console.log("called"); //not being called
});
$ctrl.filter = function (p) {
//custom filter function for each item in the array of people
}
}]
I can answer any questions of provide more code if needed
angular.module('app', []).controller('ctrl', function($scope) {
var vm = this;
vm.items = [
{ name: 'Sam' },
{ name: 'Max' },
{ name: 'Tom' },
{ name: 'Henry' },
{ name: 'Jack' },
{ name: 'Kate' }
]
var counter = 1;
$scope.$watchCollection('vm.filtered', function(){
console.log('Changed' + counter++);
})
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js">
</script>
<div ng-app='app' ng-controller='ctrl as vm'>
<input type='text' ng-model='vm.filter' />
<ul>
<li ng-repeat='item in vm.filtered = (vm.items | filter : vm.filter)'>{{item}}</li>
</ul>
</div>

Angular scope.watch not working in directive

I have a angular directive that watches a list, and then creates a custom select when the list is edited. I have this working on one page, but it refuses to work on another. I dont know why - but it looks like the watch is not catching when the list has changed.
I have reproduced the error here - http://codepen.io/jagdipa/pen/Ramjez - can someone please help?!
angular.module('MyApp',['ngMaterial', 'ngMessages', 'material.svgAssetsCache'])
.controller('AppCtrl', function($scope) {
$scope.clearValue = function() {
$scope.myModel = undefined;
};
$scope.save = function() {
alert('Form was valid!');
};
var Titles =[{"Title":"Mr"},{"Title":"Master"},{"Title":"Miss"},{"Title":"Mrs"}];
$scope.titles = Titles;
});
module MyApp.Directives {
interface TcSelectListScope extends ng.IScope {
sourceList: any[];
sourceListKey: string;
sourceListValue: string;
}
export class TcSelectListController {
static $inject = [];
constructor() {
}
}
export class TcSelectList {
public restrict = "A";
public require = ["ngModel", "^form", '^^mdInputContainer', "select"];
public controller = TcSelectListController;
public controllerAs = 'selectController';
public scope = {
sourceList: "="
}
constructor(private $compile: ng.ICompileService) {
}
public compile(tElement, tAttributes) {
var $compile = this.$compile;
var _cacheService = this.cacheService;
return function postLink(scope, element, attrs, controller) {
var ngModelCtrl = controller[0];
var mdInputContainerCtrl = controller[2];
var selectCtrl = controller[3];
console.log(selectCtrl);
/*if (scope.sourceList == undefined)
{
scope.sourceList = [];
}*/
scope.$watch(scope.sourceList, scope.onModelChanged, true);
//scope.$watchCollection(scope.sourceList, scope.onModelChanged);
scope.onModelChanged = () => {
console.log('tc select list directive 2');
console.log(scope.sourceList);
if (attrs.sourceListKey == undefined) {
throw ("The source-list-key needs to be defined for tc-select-list");
}
if (attrs.sourceListValue == undefined) {
throw ("The source-list-value needs to be defined for tc-select-list");
}
var html = undefined;
html = buildSelect();
html = markSelected(html);
element.append(html);
}
element.on("click", function () {
mdInputContainerCtrl.setHasValue(true);
});
element.bind("blur", function () {
if (ngModelCtrl.$viewValue == undefined) {
mdInputContainerCtrl.setHasValue(false);
}
});
element.bind("change", function () {
mdInputContainerCtrl.setHasValue(true);
});
function buildSelect() {
var html = ``;
angular.forEach(scope.sourceList, (val, index) => {
var itemKey = scope.sourceList[index][attrs.sourceListKey];
var itemValue = scope.sourceList[index][attrs.sourceListValue];
var selected = ``;
html += `<option label="` + itemValue +
`" value="` + itemKey +
`" ` + selected +
` >` + itemValue + ` < /option>`;
});
return html;
}
function markSelected(html) {
if (ngModelCtrl.$modelValue != undefined && ngModelCtrl.$modelValue != null) {
html = html.replace(`value="` + ngModelCtrl.$modelValue + `"`,
`value="` + ngModelCtrl.$modelValue + `" selected`)
}
return html
}
}
}
static factory() {
var directive = ($compile, cacheService) => new TcSelectList($compile, cacheService);
directive.$inject = ["$compile"];
return directive;
}
}
angular.module("MyApp").directive("tcSelectList", TcSelectList.factory());
}
<div ng-controller="AppCtrl" layout="column" layout-align="center center" style="min-height: 300px;" ng-cloak="" class="selectdemoValidations" ng-app="MyApp">
<form name="myForm">
<p>Note that invalid styling only applies if invalid and dirty</p>
<md-input-container class="md-block">
<label>Favorite Number</label>
<md-select name="myModel" ng-model="myModel" required="">
<md-option value="1">One 1</md-option>
<md-option value="2">Two</md-option>
</md-select>
<div class="errors" ng-messages="myForm.myModel.$error" ng-if="myForm.$dirty">
<div ng-message="required">Required</div>
</div>
</md-input-container>
<div layout="row">
<md-button ng-click="clearValue()" ng-disabled="!myModel" style="margin-right: 20px;">Clear</md-button>
<md-button ng-click="save()" ng-disabled="myForm.$invalid" class="md-primary" layout="" layout-align="center end">Save</md-button>
</div>
<md-input-container class="no-errors">
<label>{{translations["Title"]}}</label>
<div class="tc-select">
<select name="title"
tc-select-list
ng-model="myModel.Title"
source-list="titles"
source-list-key="Title"
source-list-value="Title"></select>
</div>
</md-input-container>
<br/>
{{titles}}
<br/>
Title = {{myModel.Title}}
</forms>
</div>
I found the answer. Turns out the following line doesnt work
scope.$watch(scope.sourceList, (newVal) => {
Changing it to the following sorted the problem out
scope.$watch('sourceList', (newVal) => {

Check/ Uncheck All checkboxes - AngularJS ng-repeat

I have a structure which looks like this: http://jsfiddle.net/deeptechtons/TKVH6/
<div>
<ul ng-controller="checkboxController">
<li>Check All
<input type="checkbox" ng-model="selectedAll" ng-click="checkAll()" />
</li>
<li ng-repeat="item in Items">
<label>{{item.Name}}
<input type="checkbox" ng-model="item.Selected" />
</label>
</li>
</ul>
</div>
angular.module("CheckAllModule", [])
.controller("checkboxController", function checkboxController($scope) {
$scope.Items = [{
Name: "Item one"
}, {
Name: "Item two"
}, {
Name: "Item three"
}];
$scope.checkAll = function () {
if ($scope.selectedAll) {
$scope.selectedAll = true;
} else {
$scope.selectedAll = false;
}
angular.forEach($scope.Items, function (item) {
item.Selected = $scope.selectedAll;
});
};
});
When check all is selected or deselected, all other checkboxes are selected. But when "Check All" is selected, and I deselect one of the items, I want "Check All" to be deselected.
Thanks in advance!
(PS: Thanks to deeptechtons for the JSFiddle)
If you are using Angular 1.3+ you can use getterSetter from ng-model-options to solve this and avoid manually keeping track of the allSelected state
HTML:
<input type="checkbox" ng-model="allSelected" ng-model-options="{getterSetter: true}"/>
JS:
var getAllSelected = function () {
var selectedItems = $scope.Items.filter(function (item) {
return item.Selected;
});
return selectedItems.length === $scope.Items.length;
}
var setAllSelected = function (value) {
angular.forEach($scope.Items, function (item) {
item.Selected = value;
});
}
$scope.allSelected = function (value) {
if (value !== undefined) {
return setAllSelected(value);
} else {
return getAllSelected();
}
}
http://jsfiddle.net/2jm6x4co/
You can use this function to check if all records are checked whenever a checkbox changes:
$scope.checkStatus= function() {
var checkCount = 0;
angular.forEach($scope.Items, function(item) {
if(item.Selected) checkCount++;
});
$scope.selectedAll = ( checkCount === $scope.Items.length);
};
The view code:
<input type="checkbox" ng-model="item.Selected" ng-change="checkStatus();"/>
http://jsfiddle.net/TKVH6/840/
working example: http://jsfiddle.net/egrendon/TKVH6/845/
view:
<input type="checkbox" ng-model="item.Selected" ng-click="setCheckAll(item)" />
controller:
angular.module("CheckAllModule", [])
.controller("checkboxController", function checkboxController($scope) {
$scope.Items = [{
Name: "Item one"
}, {
Name: "Item two"
}, {
Name: "Item three"
}];
$scope.checkAll = function () {
if ($scope.selectedAll) {
$scope.selectedAll = true;
} else {
$scope.selectedAll = false;
}
angular.forEach($scope.Items, function (item) {
item.Selected = $scope.selectedAll;
});
};
$scope.setCheckAll = function (item) {
//
// Check if checkAll should be unchecked
//
if ($scope.selectedAll && !item.Selected) {
$scope.selectedAll = false;
}
//
// Check if all are checked.
//
var checkCount = 0;
angular.forEach($scope.Items, function(item) {
if(item.Selected) checkCount++;
});
$scope.selectedAll = ( checkCount === $scope.Items.length);
};
});
Here is a neat little way of doing this
http://jsfiddle.net/TKVH6/850/
Use a little bit of undescore (just the reduce) and ng-checked
<input type="checkbox" ng-model="selectedAll" ng-click="checkAll()" ng-checked="allSelected()"/>
$scope.allSelected = function () {
var allSelect = _.reduce($scope.Items, function (memo, todo) {
return memo + (todo.Selected ? 1 : 0);
}, 0);
return (allSelect === $scope.Items.length);
};
You could use javascripts built in .reduce if you did not want to use underscore.
This answer has similar logic to Kmart2k1's answer. It puts the responsibility of updating the master checkbox on each child in the ng-repeat. Instead of using array.forEach, I use array.some to more quickly check if any of the items are unselected.
HTML:
<li ng-repeat="item in Items">
<label>{{item.Name}}
<input type="checkbox" ng-model="item.Selected" ng-change="notifyMaster()"/>
</label>
</li>
Javascript
$scope.notifyMaster = function () {
$scope.selectedAll = !$scope.Items.some(function (item) {
return !item.Selected; //check if ANY are unchecked
});
}
Forked fiddle

exact filter in angular

In Angular, is there a way to modify the filter such that it only returns exact matches?
Example:
var words = [
{ title: "ball" },
{ title: "wall" },
{ title: "all" },
{ title: "alloy" }
];
var wordsFiltered = filter('filter')
(
words,
{
'title': 'all'
}
);
The above will match 'ball', 'wall', 'all' and 'alloy'. But I would like it to only match 'all'. Any way to change it?
UPDATE
Starting from AngularJS v.1.1.3 the exact filtering is provided natively:
Find words that exactly match title:
<input ng-model="match.title" />
<br>
and exactly match type:
<input ng-model="match.type" />
<hr>
<table>
<tr ng-repeat="word in words | filter:match:true">
<td>{{word.title}}</td>
</tr>
</table>
Plunker
Your question implies that you would want to match against multiple object properties so here's a filter that does that:
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.match = {};
$scope.words = [
{ title: "ball", type: 'object' },
{ title: "wall", type: 'object' },
{ title: "all", type: 'word' },
{ title: "alloy", type: 'material' }
];
}
]
);
app.filter('exact', function(){
return function(items, match){
var matching = [], matches, falsely = true;
// Return the items unchanged if all filtering attributes are falsy
angular.forEach(match, function(value, key){
falsely = falsely && !value;
});
if(falsely){
return items;
}
angular.forEach(items, function(item){ // e.g. { title: "ball" }
matches = true;
angular.forEach(match, function(value, key){ // e.g. 'all', 'title'
if(!!value){ // do not compare if value is empty
matches = matches && (item[key] === value);
}
});
if(matches){
matching.push(item);
}
});
return matching;
}
});
<body ng-controller="AppController">
Find words that exactly match title:
<input ng-model="match.title" />
<br>
and exactly match type:
<input ng-model="match.type" />
<hr>
<table>
<tr ng-repeat="word in words | exact:match">
<td>{{word.title}}</td>
</tr>
</table>
</body>
PLUNKER
Try this :
var words = [
{ title: "ball" },
{ title: "wall" },
{ title: "all" },
{ title: "alloy" }
];
var wordsFiltered = filter('filter')
(
words,
{
'title': 'all'
},
true
);
You can use Regex to achieve a simple implementation:
<input ng-model="query" />
<tr ng-repeat="word in words | filter: myFilter">
In the controller:
$scope.myFilter = function (word) {
if ($scope.query === '') return true;
var reg = RegExp("^" + $scope.query + "$");
return reg.test(word.title);
};
I would create a new filter. Is this what you want?
HTML
<div ng-controller="MyCtrl">
{{words | exactMatch:'all'}} !
</div>
JavaScript
var myApp = angular.module('myApp',[]);
myApp.filter('exactMatch', function() {
return function(words, pattern) {
var result = [];
words.forEach(function (word) {
if (word.title === pattern) {
result.push(word);
}
});
return result;
}
});
function MyCtrl($scope) {
$scope.words = [
{title: "ball", other: 1},
{title: "wall", other: 2},
{title: "all", other: 3},
{title: "alloy", other: 4},
{title: "all", other: 5},
];
}
JsFiddle: jsfiddle
More information about custom filters: filters, creating custom filters and using filters
If you want use filter in Javascript instead of html you should look here: jsfiddle
I created my own filter.
<select
ng-model="selection.neType"
ng-options="option.NE_TYPE_ID as option.NAME for option in networkElementTypes">
<option value="">Todos</option>
</select>
<select
ng-model="selection.neTypeVendor"
ng-options="option.VENDOR_TYPE_ID as option.NAME for option in networkElementTypeVendors | exactMatch: {FK_NE_TYPE_ID: selection.neType}">
<option value="">All</option>
</select>
app.filter('exactMatch', function() {
return function(elements, pattern) {
var result = [];
var fieldSearch = Object.keys(pattern)[0];
elements.forEach(function (element) {
if (element[fieldSearch] == pattern[fieldSearch]) {
result.push(element);
}
});
return result;
}
});
angular-filter is a useful library of filters.
One of their filters is the where filter that does an exact match.

Resources