md-select component loses ng-selected value - angularjs

I work with Angular 1.5.4 & material 1.0.6. Everything of the following example is working fine if I use chrome. But if I use safari no value is preselected.
The template of the component looks like
<md-select ng-model="$ctrl.selectedFruit">
<md-option ng-repeat="fruit in $ctrl.fruits" ng-value="fruit" required
ng-selected="$first" >
{{fruit.name}} {{fruit.weight}}
</md-option>
</md-select>
the component looks like
.component('applicantAccount', {
// #ngInject
bindings: {
selectedFruit: "="
},
controller: function (accountService) {
var ctrl = this;
fruitService.initFruitBefore(function () {
ctrl.fruits= fruitService.getFruits();
});
},
templateUrl: 'views/templates/selectFruitTemplate.html'
});
fruitService:
var fruits = [];
var initFruits = function () {
return $http.get(configuration.apiUrl + "fruits")
.then(function success(response) {
fruits = response.data || []
return response;
})
};
var getFruits = function () {
var filterEx = {type: "fresh"};
return $filter('filter')(fruits , filterEx);
};
var initFruitBefore = function (callback) {
if (!areFruitsAvailable()) {
initFruits().then(callback);
}
else{
callback();
}
};
var areFruitsAvailable = function () {
return typeof fruits[0] !== 'undefined'
};
var getFreshFruit = function () {
var filterEx = {type: "fresh"};
var filtered = $filter('filter')(fruits, filterEx);
return filtered[0] || null;
};
controller:
fruitService.initFruitBefore(function () {
ctrl.selectedFruit = accountService.getFreshFruit();
ctrl.fruits = accountService.getFruits();
});
The odd thing(only safari) is, that ctrl.fruits gets initialized before selectedFruit is initialized (outside of the component). If this happens the value was shown for a short term.
Has anyone an idea whats going on? Why behaves the safari different?
Thank you & sorry for my bad english
The Solution
I added ng-model-options={trackBy:'$value.id'}.
Angular Material:
To determine whether something is selected, ngModelController is looking at whether $scope.selectedUser == (any user in $scope.users);;
Javascript's == operator does not check for deep equality (ie. that all properties on the object are the same), but instead whether the objects are the same object in memory. In this case, we have two instances of identical objects, but they exist in memory as unique entities. Because of this, the select will have no value populated for a selected user.
For futher description look at https://material.angularjs.org/latest/api/directive/mdSelect

Related

Pass parametar througth function and show it

I have problem with passing params with one function to another.
I am passing params like this:
<div ng-click="changeView('distance')">EXPAND
</div>
And in my controller
$scope.changeView = function (params) {
$scope.currentView = params;
};
I have more pages with more option. In my option I am selecting team, but I want to show depending on my changeView.
This is my select for teams
<select style="width: 100%;" ng-model="selectedTeam" ng-options="x.team_name for x in teams" ng-change="changeLocation(selectedTeam.team_id);"
</select>
And this is in my controller for changeLocation
$scope.changeLocation = function (teamId) {
$scope.showPlayer = true;
if ($scope.currentView == undefined) {
$state.go('activity-statistic.activity-statistic-team-heart', { teamId: teamId }, { reload: false });
}
else {
$state.go('activity-statistic.activity-statistic-team-' + $scope.currentView + '', { teamId: teamId }, { reload: false });
}
};
When I want to change location, my $scope.curentView is undefined always.
Any idea what I'm doing wrong?
Its looks like that your $scope.changeView and $scope.changeLocation both belong to different controllers $scope has its scope only in the current controller that why its showing undef
You can use $rootscope instead for global scope something like
$scope.changeView = function (params) {
$rootscope.currentView = params;
};
then change
if ($scope.currentView == undefined)
to
if ($rootscope.currentView == undefined)

Check for empty ng-repeat from controller

I'm a newbie in angularjs. And my template is something like this:
<div ng-repeat="item in values"></div>
<div ng-repeat="item1 in values1"></div>
And my controller:
$scope.openItems = function () {
$http.get('/api/openItems').success(function (data) {
$scope.values = data;
});
};
That is working fine. Now I want to show the items, only if it is empty.
I tried something like this:
$scope.openItems = function () {
$http.get('/api/openItems').success(function (data) {
if ($scope.values.length == 0) {
$scope.values = data;
} else {
$scope.values1 = data;
}
});
};
Any idea how to check from the controller is a ng-repeat has data in it?
Thanks in advance
Make sure you are initializing the arrays at the top, then you can just do something like this :
//initialize vars
$scope.values = [];
$scope.values1 = [];
$scope.openItems = function () {
$http.get('/api/openItems').success(function (data) {
if ($scope.values.length === 0) {
//you may or may not want to clear the second array if you are toggling back and forth
$scope.values1 = [];
$scope.values = data;
} else {
//empty the first one so we make the hide/show logic simple
$scope.values = [];
$scope.values1 = data;
}
});
};
then your html just looks like
<div ng-show="values.length" ng-repeat="item in values"></div>
<div ng-show="values1.length" ng-repeat="item1 in values1"></div>
Here is a quick proof of concept - http://jsfiddle.net/Lzgts/573/
You can also swap the ng-show with ng-if, if you want the divs to actually be taken off the DOM.
Depending on how your array is initialized (which we're not seeing) it might not ever have length==0 (for example I think it could be undefined, etc.) you could try:
if ($scope.values.length != 0) {
$scope.values1 = data;
} else {
$scope.values = data;
}

AngularJS v1.3 breaks translations filter

In Angular v1.2 I was using the following code for serving up localised strings in the application:
var i18n = angular.module('i18n', []);
i18n.service('i18n', function ($http, $timeout) {
/**
A dictionary of translations keyed on culture
*/
this.translations = {},
/**
The current culture
*/
this.currentCulture = null,
/**
Sets the current culture, loading the associated translations file if not already loaded
*/
this.setCurrentCulture = function (culture) {
var self = this;
if (self.translations[culture]) {
$timeout(function () {
self.currentCulture = culture;
});
} else {
$http({ method: 'GET', url: 'i18n/' + culture + '/translations.json?' + Date.now() })
.success(function (data) {
// $timeout is used here to defer the $scope update to the next $digest cycle
$timeout(function () {
self.translations[culture] = data;
self.currentCulture = culture;
});
});
}
};
this.getTranslation = function (key) {
if (this.currentCulture) {
return this.translations[this.currentCulture][key] || key;
} else {
return key;
}
},
// Initialize the default culture
this.setCurrentCulture(config.defaultCulture);
});
i18n.filter('i18n', function (i18n) {
return function (key) {
return i18n.getTranslation(key);
};
});
In the template it is then used as follows:
<p>{{ 'HelloWorld' | i18n }}</p>
For some reason that I can't fathom, upgrading to v1.3 of AngularJS has broken this functionality. Either the $timeout isn't triggering a digest cycle, or the filter isn't updating. I can see that the $timeout code is running, but the filter code never gets hit.
Any ideas why this might be broken in v1.3?
Thanks!
In angular 1.3 the filtering was changed so that they are no longer "stateful". You can see more info in this question: What is stateful filtering in AngularJS?
The end result is that filter will no longer re-evaluate unless the input changes. To fix this you can add the line:
i18n.filter('i18n', function (i18n) {
var filter = function (key) {
return i18n.getTranslation(key);
};
filter.$stateful = true; ///add this line
return filter;
});
Or else implement your filter some other way.

Should I return collection from factory to controller via function?

I'm struggling with choosing the correct way of accessing collection located inside service from controller. I see two options, both have ups and downs:
Returning function from service that returns collection:
Service:
app.factory('healthService', function () {
var healths = [{},{},{},{}];
function updateHealths() {
healths = [...];
}
return {
getHealths : function() {
return healths;
},
update : function () {
updateHealths();
}};
});
Controller:
$scope.healths = healthService.getHealths;
$scope.update = healthService.update;
View:
ng-repeat = "health in healths()"
ng-click = "update()" '
I'm not sure about efficiency here- how often will healths() be evaluated?
Giving the possibility to access collection directly from controller:
Service:
app.factory('healthService', function () {
return {
healths : [{},{},{},{}],
update :function() {
this.healths = [...];
}
});
Controller:
$scope.healthService = healthService;
View:
ng-repeat = "health in healthService.healths" '
ng-click = "healthService.update()"
Which one is better, faster? Any other tips?
Why not try wrapping your collection in an object (which acts as a service state) to allow binding to occur on the object, rather than by exposing functions. For example:
app.factory('healthService', function() {
var state = {
healths: [...]
};
return {
getState: function() { return state; },
updateHealths: function() { state.healths = [...]; }
};
});
Then inside your controller:
$scope.healthState = healthService.getState();
Then to reference your healths from your html, use:
<div ng-repeat="health in healthState.healths"></div>

Angular Directive With Binding to Hash of Scope Objects

I'm trying to create a directive that will allow me to bind to hash of other scope properties.
HTML
<div lookup lookup-model="data.countryId"></div>
<div lookup lookup-model="data.stateId" lookup-params="{countryId: data.countryId}"></div>
What I would like to be able to do is every time a value in lookup-params is updated to refresh the lookup with the model of data.stateId. I'm trying to keep this generic since there is likely a variety of different lookup-params I'll want to have.
Is there a way to do this in Angular?
Update
I certainly didn't provide enough detail. Clicked submit too soon. Here is my solution based off feedback from #Olivvv. The suggestion led me to the scope.$eval function.
The goal here was to create a directive that would allow us to use a select with a $http get to seed the options within the select. Some of the $http requests will need a parameter since their is a dependency on another value. For example, a set of states can only be provided when a country value is provided.
The following is the code I pulled together. I'm sure it can be improved, but it is doing the trick at the moment. Please note, I'm using Lodash for some utility functions. You'll also see a scope object "lookupModelObject". This was purely to meet a design need for styling selects. It probably can be ignored if you are only interested in the lookupParams.
HTML Snippet
<div select-lookup lookup-value="block.data.countryId" lookup-type="countries" lookup-placeholder="Select a Country"></div>
<div select-lookup lookup-value="block.data.stateId" lookup-type="states" lookup-params="{countryId: block.data.countryId}" lookup-placeholder="Select a State"></div>
Directive
The important part to point out is how I'm evaluating the attrs.lookupParams. If the attribute exists I use scope.$eval to evaluate the attribute. You'll later see how I added each parameter to scope and added a watcher in case one of the params changed. This would allow me to reset the "state" to null if a different country was selected.
angular.module("foo").directive('selectLookup', ['$http', '$q', function($http, $q) {
return {
restrict: 'A',
replace: true,
templateUrl: 'common/lookups/partials/selectLookupPartial.html',
scope: {
id: "#",
lookupValue: "=",
lookupParams: "="
},
link: function(scope, element, attrs) {
// Initialize the options.
scope.options = [];
var optionsLoaded = false;
var resetLookupModel = function() {
scope.lookupModelObject = {
id: null,
text: attrs.lookupPlaceholder
};
};
// Evaluate and obtain the lookup parameters for this lookup.
var lookupParams = {};
if (attrs.lookupParams) {
lookupParams = scope.$eval(attrs.lookupParams);
}
var updateLookupModelObject = function(value) {
// This function is only relevant if the options have been loaded.
if (optionsLoaded) {
if (value === undefined || value === null) {
resetLookupModel();
}
else {
var item = _.findWhere(scope.options, {id: value});
if (item) {
scope.lookupModelObject = item;
}
else {
resetLookupModel();
}
}
}
};
var fetchValues = _.throttle(function() {
var deferred = $q.defer(),
fetchUrl = "/api/lookup/" + attrs.lookupType,
keys = _.keys(lookupParams);
_.each(keys, function(key, index) {
if (index === 0) {
fetchUrl += "?";
}
else {
fetchUrl += "&";
}
fetchUrl += key + "=" + lookupParams[key];
});
// Empty the options.
scope.options.splice(0, scope.options.length);
$http.get(fetchUrl).then(function(response) {
scope.options = response.data;
optionsLoaded = true;
updateLookupModelObject(scope.lookupValue);
deferred.resolve(scope.options);
});
return deferred.promise;
}, 150);
// Setup the watchers
// If there are lookup params add them to scope so we can watch them.
var keys = _.keys(lookupParams);
_.each(keys, function(key) {
scope[key] = lookupParams[key];
// Setup watchers for each param.
scope.$watch(key, function() {
fetchValues();
});
});
scope.$watch('lookupParams', function(newValue, oldValue) {
if (!_.isEqual(newValue, oldValue)) {
lookupParams = newValue;
fetchValues().then(resetLookupModel);
}
});
scope.$watch('lookupValue', updateLookupModelObject);
scope.$watch('lookupModelObject', function(newValue, oldValue) {
if (!_.isEqual(newValue, oldValue)) {
scope.lookupValue = newValue.id;
}
});
fetchValues();
}
};
}]);
Template
We had a design constraint that forced us to introduce a "select-placeholder". Outside of that the select is the "typical" way to setup a select in Angular.
<div class="container select-container">
<div class="select-placeholder">
<span class="select-placeholder-text">{{ lookupModelObject.text }}</span>
<select class="select-input" data-ng-model="lookupModelObject" id="{{ id }}" data-ng-options="option as option.text for option in options"></select>
</div>
</div>
I solved it with the following function in a "utils" service:
function utils ($parse) {
this.$params = function(attrs, attrName, allowedParams){
var out = {},
parsed = $parse(attrs[attrName])();
if (typeof parsed === 'object'){
angular.forEach(parsed, function(val, key){
if (allowedParams.indexOf(key) !== -1){
this[key] = val;
} else {
//do some logging. i.e ('parameter not recognized :', key, ' list of the allowed params:', allowedParams)
}
}, out);
}else{
out[allowedParams[0]] = attrs[attrName];
}
return out;
};
}
use it that way in your template:
lookup-params="{countryId: '{{data.countryId}}'}"
and in your directive :
var lookupParams = utils.$params(attrs, 'lookup-params', ['countryId','anotherParams', 'etcetera']);
The first of the allowed params can be passed directly as string instead of an object:
lookup-params="{'{{data.countryId}}'}"
will work

Resources