Binding ng-model inside ng-repeat loop in AngularJS - javascript

I'm trying to deal with the issue of scope inside of an ng-repeat loop - I've browsed quite a few questions but have not quite been able to get my code to work.
Controller code:
function Ctrl($scope) {
$scope.lines = [{text: 'res1'}, {text:'res2'}];
}
View:
<div ng-app>
<div ng-controller="Ctrl">
<div ng-repeat="line in lines">
<div class="preview">{{text}}{{$index}}</div>
</div>
<div ng-repeat="line in lines">
<-- typing here should auto update it's preview above -->
<input value="{{line.text}}" ng-model="text{{$index}}"/>
<!-- many other fields here that will also affect the preview -->
</div>
</div>
</div>
Here's a fiddle: http://jsfiddle.net/cyberwombat/zqTah/
Basically I have an object (it's a flyer generator) which contains multiple lines of text. Each line of text can be tweaked by the user (text, font, size, color, etc) and I want to create a preview for it. The example above only shows the input field to enter text and I would like that to automatically/as-you-type update the preview div but there will be many more controls.
I am also not sure I got the code right for the looping index - is that the best way to create a ng-model name inside the loop?

For each iteration of the ng-repeat loop, line is a reference to an object in your array. Therefore, to preview the value, use {{line.text}}.
Similarly, to databind to the text, databind to the same: ng-model="line.text". You don't need to use value when using ng-model (actually you shouldn't).
Fiddle.
For a more in-depth look at scopes and ng-repeat, see What are the nuances of scope prototypal / prototypical inheritance in AngularJS?, section ng-repeat.

<h4>Order List</h4>
<ul>
<li ng-repeat="val in filter_option.order">
<span>
<input title="{{filter_option.order_name[$index]}}" type="radio" ng-model="filter_param.order_option" ng-value="'{{val}}'" />
{{filter_option.order_name[$index]}}
</span>
<select title="" ng-model="filter_param[val]">
<option value="asc">Asc</option>
<option value="desc">Desc</option>
</select>
</li>
</ul>

Related

Angular 2+ switching betweens elements, based on click

So, I'm trying to use a angular way to send the buttons created to either the #start or #finish divs, based on click on the buttons selected, so in a way they would send themselves if you may if they follow a condition, which in this case is to be either inside of the #start or #finish divs. With Jquery, I just check what's the parent of certain element, if matches one, I send it to the other, and vice versa. Now with angular, I have been looking into the whole, rendering and stuff, but my head can't just understand the overall picture, and I even though I was able to click and send the element clicked to a different div, I couldn't do it with the other buttons created, and also with the button that was first clicked, in the other div.
<div class="ui raised very padded text container segment"
#finish>
</div>
<div class="ui raised very padded text container segment" #start>
<button
*ngFor='let word of ge_array'
(click)="goToNext()"
>{{word}}</button>
</div>
Does anybody know how to tackle this situation?
Renan, I would change all your plan. I have an array of element. this elements have a property "place". I will show in two div one for "plan1" and the other for "plan2".
//the .ts is like
items:any[]=[{word:'uno',place:1},{word:'dos',place:1},{word:'tres',place:2}]
get items1(){
return this.items.filter(it=>it.place==1);
}
get items2(){
return this.items.filter(it=>it.place==2);
}
//the .hmtl like
<h2>place 1</h2>
<button *ngFor="let item of items1" (click)="item.place=2">{{item.word}}</button>
<h2>place 2</h2>
<button *ngFor="let item of items2" (click)="item.place=1">{{item.word}}</button>
I would prefer using a getter and have "item1" and "item2". the click make that the property "place" of the item becomes 2 in place 1 and becomes 1 in place2
You can make two *ngFor over the same array also using a *ngIf like
<h1>place 1</h1>
<div *ngFor="let item of items">
<button *ngIf="item.place==1" (click)="item.place=2">{{item.word}}</button>
</div>
<h1>place 2</h1>
<div *ngFor="let item of items">
<button *ngIf="item.place==2" (click)="item.place=1">{{item.word}}</button>
</div>
and forget the getters

Angular Dragula and ng-repeat $index

I have an ng-repeat of rows. I set them up so you can reorder them with drag-and-drop using Angular Dragula. This works fine, but the ng-repeat $index remains the initial value for each item after dragging. See screen captures below before and after a drag of the "Recipient" row.
Is there a way to update the index? I also want to re-order the menu with javascript, with that button in each row, but for that to work I need to get the current $index (and decrement/increment it) and that won't work if the index is incorrect like this.
Before Drag
After Drag
For what it's worth, this is the answer:
<div class="table-cards-body" dragula="'first-bag'" dragula-model="home.quickReceiveFields">
<div class="table-cards-row drag-item" ng-repeat="field in home.quickReceiveFields track by $index">
<div class="one">{{field.name}}</div>
<div class="two">{{field.type}}</div>
<div class="three">{{field.default}} {{$index}}</div>
<div class="four"><input type="checkbox" ng-model="field.display"></div>
<div class="five"><input type="checkbox" ng-model="field.retain"></div>
<button class="btn btn-default btn-xs" ng-click="home.moveItemUp($index)">^</button>
</div>
</div>
Despite what the docs say on the angular-dragula site, you need to put the dragula-model attribute on the container into which you put the repeating items. Not the ng-repeat itself.
Try to add in ng-repeat track by $index.

How can I have arbitrary content inside an ng-repeat with Angular?

I have an ordered list of items that I want to be displayed. Getting this done with Angular is trivial:
<p>Filter a game: <input type="text" ng-model="search.name" /></p>
<game-card ng-repeat="game in games | filter:search" game ="game" size="large"></game-card>
This outputs an ordered list of games that has been sorted from the database. However, I want to show a few snippets of arbitrary content inside this ng-repeat. This diagram below indicates what I'm after. The lefthand side shows how it should look in the unfiltered state, the righthand side if a search term as present.
As you can see, I want to highlight the first item with a "Next up" heading, and following that, an "After that" heading. When a search is present, I don't want these headings at all as they won't be relevant.
I'm completely new to Angular ("you lost me at service" new, by the way), but have come from a Knockout.js background. What is the "Angular way" of solving this?
Stack up ng-if's on the headings and compare with $index? Does $index even care about the filter being applied?
Kind of dirty solution: within the ng-repeated element, put something like
<div ng-if="$index === 0">
<h2>Next up</h2>
</div>
<div ng-if="$index === 1">
<h2>After that</h2>
</div>
<div>
<!-- Your content goes here, unconditionally -->
</div>
And the obvious problem with this is that two tests are performed for each "instantiation" of the content of the ng-repeated element, and they are useless except for the first and second.
Otherwise you could place your first two bits of contents with headers outside the ng-repeat and then start repeating from index 2, but I'm not sure that's possible with ng-repeat.
If it's not, then split your data into firstElement, secondElement and rest where rest is Array.prototype.slice.call(/* your initial list of elements */, 2).
The problem with this last solution is that it requires you to adapt your data to how you present it, which is undesirable for obvious reasons.
edit: OK here's something better, not tested:
<div ng-if="elements.length > 0">
<h2>Next up</h2>
<div><!-- First content goes here, it uses elements[0] --></div>
</div>
<div ng-if="elements.length > 1">
<h2>After that</h2>
<div><!-- Second content goes here, it uses elements[1] --></div>
</div>
<div ng-repeat="element in elements.slice(2)">
<!-- The rest goes here, unconditionally -->
</div>
Instead of doing your ng-repeat in the game-card tag, do it in a wrapping div and then use ng-repeat special variables:
<p>Filter a game: <input type="text" ng-model="search.name" /></p>
<div ng-repeat="game in games | filter:search">
<div ng-if="$first">NEXT UP</div>
<div ng-if="$index === 1">AFTER THAT</div>
<game-card game="game" size="large"></game-card>
</div>
My solution was to ng-show the titles only if the length of the results passed through the filter equaled the total number of games:
<h2 ng-show="games.indexOf(game) == 1 && search.length == games.length">More Games</h2>

Using ng-model (or other solution) without breaking MVC paradigm

I recently asked a question on Stack where I was trying to obtain a DOM element's ID via AngularJS' ng-click. Essentially the answer which was given (with an important caveat was):
Use event.currentTarget vs event.target to get the element to which the binding was registered, BUT this is an atypical way to do it because it ties the controller to the DOM when ideally the controller should know nothing about the DOM.
I'm starting to get a better idea of this now, but would like some further help / clarification.
Using ng-repeat I dynamically render a number of tiles being pulled from a database and present them to the user for selection. When a user clicks on a given tile I want to be able to 'know' that element's ID (or some unique identifier key) so that I can pass it into my javascript / java and then retrieve the details for said key where they are rendered in a different, more detailed view.
I've started to research ng-model which supports the two-way MVC idea, but I'm stuck. You can see below that I'm dynamically rendering each tile with a different ng-model value which equals the tile's database key. Is this the solution I want? If so, how can I reference the ng-model value in javascript? Or if I do that, am I breaking the MVC again? If that's the case, what would be a solution which preserves the model?
Cheers
HTML:
<div ng-repeat="tile in tileResult">
<div ng-model={{tile.id}} ng-click="handleThisElement($event); changeView('panel3')" class="container-fluid btn-default tile">
<div class="row">
<div class="col-xs-9">
<div class="row">
...
</div>
</div>
<div class="col-xs-3 tile-stats">
<div class="row text-center">
...
</div>
</div>
</div>
</div>
</div>
Tie the ng-model to an object, best if it's something in the repeater. Also, ng-model is generally used with an input... not on a div, so I'm not sure what you're trying to achieve here.
You may want to initialize the value to the index (or some other default) if the value doesn't exist, this will avoid null pointers when you want to change the value later.
<div ng-repeat="tile in tileResult">
<div ng-model="tile.someDataValue" ng-init="tile.someDataValue = $index" ng-click="handleThisElement($event); changeView('panel3')" class="container-fluid btn-default tile">
...
</div>
</div>
To later reference the value, you can just access your tileResult object at the appropriate value/index
Ex:
console.log($scope.tileResult[0].someDataValue);
Or you can access the entire 'tile' on click by passing the 'tile' into a function. Ex:
<div ng-model="tile.someDataValue" ng-init="tile.someDataValue = $index" ng-click="someFunction(tile); handleThisElement($event); changeView('panel3')" class="container-fluid btn-default tile">
$scope.someFunction = function(someTile){
console.log(someTile.id, someTile); // log the id, then the entire object
}
If I understand you correctly, you want to be able to access the unique identifier for each tile.
This can easily be done without Ng-Model! One easy fix would be to set the id of each element with the unique identifier:
<div ng-repeat="tile in tileResult">
<div id="{{tile.id}}" ng-click="handleThisElement($event); changeView('panel3')" class="container-fluid btn-default tile">
<div class="row">
This way, when you pass $event to your handleThisElement function, you are able to access the id in the same way you have before.

Angular "new row" functionality

I'm currently building a widget for a site that's effectively a "deal builder". In the builder widget will be a "add new item" button which will add a new device item to the <li> list.
<ul class="deal-builder-devices entity">
<li ng-repeat="device in devices">
<div class="db-handset-image">
<span class="phone-silhouette" ng-hide="hideSilhouette"></span>
<img ng-repeat="image in modelImages" src="[[image]]" ng-hide="!hideSilhouette" />
</span>
</div>
<div class="db-device">
<ul class="opts">
<li>
<select ng-model="selectedManufacturer" ng-change="getManufacturerModels(selectedManufacturer)">
<option value="">Manufacturer</option>
<option ng-repeat="manufacturer in manufacturers" value="[[manufacturer.id]]">[[manufacturer.name]]</option>
</select>
</li>
<li>
<select ng-disabled="!models > 0" ng-model="selectedModel" ng-change="loadModelImage(selectedModel)">
<option value="">Model</option>
<option ng-repeat="model in models" value="[[model.id]]">[[model.model]]</option>
</select>
</li>
</ul>
</div>
</li>
</ul>
<div class="deal-builder-controls entity">
<button class="db-add-handset" ng-click="addDevice()"><i class="fa fa-plus-circle"></i> Add another handset</button>
<button class="db-find-deals">Find deals</button>
</div>
The issue that I'm having is that it gives the select options the exact same model, so if I change the select option in one dropdown, it will change all newly generated dropdowns too. How do I fix this?
This is how $scope.addDevice(); is currently working:
$scope.devices = [0];
$scope.devicesCounter = 0;
$scope.addDevice = function () {
$scope.devicesCounter++;
$scope.devices.push($scope.devicesCounter);
}
you have the same model on all the dropboxes so that is normal
use in
ng-model="device.selectedModel"
then it will set it to the device and it should work.
It would be best if you push images, manufacturers and models inside of $scope.devices.
Also use angular.copy() to copy object instead of passing reference. That way you can modify each object individually.
One more thing i changed is track by $index after ng-repeat. This is necessary since we are pushing identical object into array which is not allowed within ng-repeat. You can change devices array to object with some generic keys if you want to avoid track by.
And lastly, set ng-model to device.childProperty. Now you have all data you need inside devices array.
Here is the example plunk

Categories

Resources