I'd like to use a configuration object to display a list. The problem is that this list has different types of elements it can contain: item, divider, and subheader.
A simple configuration for this list might be:
var list = [
"item",
"item",
"divider",
"subheader",
"item",
"item"
];
How do I write an ngRepeat attribute that would output something like this:
<md-menu-content>
<md-menu-item>...</md-menu-item>
<md-menu-item>...</md-menu-item>
<md-menu-divider></md-menu-divider>
<md-subheader>...</md-subheader>
<md-menu-item>...</md-menu-item>
<md-menu-item>...</md-menu-item>
</md-menu-content>
I'm not even sure this is possible. Looking at the documentation for ngRepeat, it can only be used as an attribute, so something like <ng-repeat>...</ng-repeat> is not an option. md-menu-item, md-menu-divider, and md-subheader also have to be elements as they're part of Angular Material and even if list item and the subheader aren't specifically restricted to being an element (unlike the divider), their CSS styles are written to be for elements only.
I've also played around with ngRepeatStart, but that's an alternative to ngRepeat, and they, as far as I can see, cannot be used together so that ngRepeat would be one level above ngRepeatStart.
Using the directive from this answer, you can do something like the following:
$scope.list = [
"item",
"item",
"divider",
"subheader",
"item",
"item"
];
$scope.attrs = [];
for(var i = 0; i < $scope.list.length; i++){
$scope.attrs.push([{attr: 'md-menu-' + $scope.list[i], value: ''}]);
}
html:
<div ng-repeat="item in attrs" dyn-attr="item">
something
</div>
See this jsfiddle
Of course you can tweak this however you want. The directive of the linked answer adds attributes based on a list, but you'll probably want to create a directive that just uses something like dyn-md-attr="item"
Using the answer by #devqon I have a partial solution to this problem that works, but generates a lot of unnecessary digests that need to be dealt with. Nevertheless, it creates dynamic element with a directive that replaces the current element.
The fiddle is here: http://jsfiddle.net/qk96y2rw/
Here's the code:
myApp.directive('dynAttr', function() {
return {
scope: { list: '=dynAttr' },
link: function(scope, elem, attrs){
//console.log(scope.list);
for(attr in scope.list){
if (scope.list[attr].attr) {
console.log(scope.list[attr].attr);
var replacementElement = document.createElement(scope.list[attr].attr);
replacementElement.innerHTML = elem.html();
elem.replaceWith(replacementElement);
}
}
}
};
});
Related
I have a dataset that looks like this:
[
{
'title' : 'cats',
'names' : [
'felix',
'tom',
... more names
]
},
{
'title' : 'dogs',
'names' : [
'fido',
'rover',
... more names
]
},
{
... More animal types
]
And I would like to have the following:
<p ng-repeat='name in names'>{{ name }}</p>
But, to do that I really need to at some stage set
$scope.names = ['felix', 'tom', 'fido', rover'];
My question is: is there an 'Angular' way to merge arrays or take content from multiple places from one object? Or do I need to use a for loop with a concat function to create the array I use?
Sure, just defined names based on your data, demo.
$scope.names = function() {
return Array.prototype.concat.apply([], animals.map(function(animal) {
return animal.names;
}));
};
Then use that method in your view
<p ng-repeat='name in names()'>{{ name }}</p>
Or we could assume the list of animals won't change, and use a library like lodash for readability, demo.
$scope.names = _.chain(animals)
.pluck('names')
.flatten()
.value()
It is not likely to be a core functions for such feature in javascript nor in angular as angular is just mvc framework on javascript. function you need is not common enough for javascript, or specific enough for mvc
I'm using the following angular code to get one item out of a list.
<span ng-repeat="metro in metros | filter:{id:event.metro_id}">
{{metro.timezone}}
</span>
So there's a list of metros and I'm filtering the list for metros where the id matches event.metro_id. The thing is, for my scenario I know there's always going to be 1, and only 1, match for this filter and it feels silly to use a ng-repeat just to get that one element. Is there a more elegant way to do this. My goal is to do it all in the HTML template and avoid placing more code in the angular controller.
If you could slightly modify your metros structure (only in favour of what you want to), to be a collection with id's as properties, then you could do like this in HTML:
<span ng-show="event.metro_id">
{{metros[event.metro_id].timezone}}
</span>
I meant to have metros like this:
$scope.metros = {
"25" : {
"timezone" : 25,
"foo" : "bar"
},
"35" : {
"timezone" : 35,
"foo" : "bar"
}
}
My goal is to create in pure AngularJS a fill-in-the-blank activity which takes collection of records that contain phrases with markers where the blanks are, the correct answers, and incorrect answers to display along with them.
I've programmed a prototype in AngularJS/jQuery, below, which has the interactive functionality that I want, also here: http://jsfiddle.net/kkLdzngv/2/
What do I need to do to now is:
replace all the jQuery code with AngularJS code
make it dynamic so that not just one phrase is displayed, but all phrases in the collection are displayed with input fields each with the functionality that is in my prototype
In particular, I don't understand the correct AngularJS approach. I assume I need to use a ng-repeat to iterate through $scope.texts and inside the ng-repeat I would have my <div class="item">. But at that point, what functionality of AngularJS do I need to use to create the functionality shown in the prototype?
HTML:
<div class="item">
<div class="text">Customer 1 is from <input id="record_1_blank_1" ng-change="checkEntry_record_1_blank_1()" ng-model="record_1_blank_1"/> and Customer 2 is from <input id="record_1_blank_2" ng-model="record_1_blank_2"/>.</div>
<div class="answers">Munich, <span id="answer2">Berlin</span>, Frankfurt, Stuttgart, Hamburg</div>
</div>
JavaScript:
$scope.texts = [
{
body: 'Customer 1 is from [##blank] and Customer 2 is from [##blank].',
correctAnswers: 'Berlin;Hamburg',
incorrectAnswers: 'Stuttgart;Munich;Frankfurt'
},
{
body: 'Company 3 is located in [##blank].',
answers: 'Bremen'
}
];
$scope.checkEntry_record_1_blank_1 = function (obj) {
if ($scope.record_1_blank_1 == 'Berlin') {
$('#record_1_blank_1').css(
{
background: 'lightgreen'
})
.attr('disabled', 'disable');
$('#record_1_blank_2').focus();
$('#answer2').css('text-decoration', 'line-through');
}
};
Typically in Meteor templates, if you supply data through a router like iron-router, you are returning a set of documents (really a cursor) that you iterate through with the #each operator. But what if you only want to return a specific element from some field that is an array? I've tried several things, including the $slice operator in my query, but nothing seems to work.
Here is a simple route that sets data to an array:
Router.map(function() {
this.route('home', {
path: '/',
template: 'home',
data: [{fruit: 'apples', count: 10}, {fruit: 'oranges', count: 5}]
});
});
The home template looks like this:
<template name="home">
<p>{{snack.fruit}}: {{snack.count}}</p>
</template>
To show 'apples' as the snack, you need only access the first element of this:
Template.home.helpers({
snack: function() {
return this[0];
}
});
Note, however, that this inside of the helpers is not actually an array - it will be an object with the same key-value pairs as the data you supplied (as well as a yield function apparently).
I've got some data like this:
people = [
{
names: [
{first: "Joe", last: "Smith"},
{first: "Joseph", last: "Smith"},
...
]
},
...
]
In other words, an array of objects with an array of names. For example, a person could be called "Joe Smith" or "Joseph Smith". How can I use a filter to only search the first element of names? IE: If I typed in "Jo" or "Smith" it would find the first person. But, if I typed in "seph" it wouldn't.
I've been looking at the examples on this page, but there isn't really an example of filtering inside arrays. Here's what I've tried but it gives me an error:
<input ng-model="search.names[0].$">
TypeError: Cannot set property '$' of undefined
Working Code
Input HTML
<input ng-model="searchTerm">
Results
<tr ng-repeat="p in people | filter:searchFunc">...</tr>
Controller
$scope.searchFunc = function(person) {
var firstPersonsName = [person.names[0]]; // Wrapping in array since the 'filter' $filter expects an array
var matches = $filter('filter')(firstPersonsName, $scope.searchTerm); // Running firstPersonsName through filter searching for $scope.searchTerm
return matches.length > 0;
}
Plunker Demo
Answer to the question in your title
I played around with filter and it doesn't seem like you can go beyond one level deep when specifying a pattern object for it e.g. ng-model="search.names" works but ng-model="search.names.otherVal" doesn't.
Also, even if filter supported going beyond one level deep, you still wouldn't be able to do ng-model="search.names[0]". This is because filter expects the input to be an array, but the elements of your names array are all objects e.g. people[0].names[0] == {first: "Joe", last: "Smith"} so filtering will never work.
The only way to do what you are asking purely through the view and no extra code in your controller is to just create your own custom filter that handles your case.
Would this do the trick?
Say that your input is
<input ng-model="search.name" type="text">
Then, you can display results like this:
<div ng-repeat="person in people[0].names | filter: alias">...</div>
Controller:
$scope.alias = function (object) {
if (object.first.match(new RegExp($scope.search.name))) {
return true;
}
return false;
};