Get new array of after swapping data in ngfor - javascript

I am using angular 4+
Here is my array
i used ngfor to populate data with draggable div
['MODEL', 'PART', 'CUSTOMER'];
what i want whenever i drag/swap one of the div then the new array become something like
['PART', 'MODEL', 'CUSTOMER'];
cant find any solution for this
my plunker
https://plnkr.co/edit/oy2QWJ?p=preview

When you update the html outside the scope of angular, you will need to put extra effort to make sure the state of template/component are in sync. You should directly update the model and as it is 2 way binding, html will be rendered automatically according to that.
You need to make following updates
JS
Add private variable dragIndex
Update drag function (set dragIndex - index of element being dragged)
public drag (index) {
this.dragIndex = index;
}
Update drop function (swap the values in array at the drag and drop indexes)
public drop (ev, index: number) {
ev.preventDefault();
let temp = this.homePageSearchTiles[this.dragIndex];
this.homePageSearchTiles[this.dragIndex] = this.homePageSearchTiles[index];
this.homePageSearchTiles[index] = temp;
console.log(this.homePageSearchTiles);
}
HTML
i passed in function was not defined. Define i as index in ngFor and pass it in both drag and drop functions
<div *ngFor="let tiles of homePageSearchTiles; let i = index; (drop)="drop($event, i)" (dragover)="allowDrop($event)">
<div class="tiles ui-g-12 ui-lg-5 card box-id" id="div{{tiles.id}}" draggable="true" (dragstart)="drag(i)">
{{tiles}}
</div>
</div>
Please find working version, Plunker

Related

Angular - Most efficient/elegant way to access component/dom element inside a changing ngb-accordion panel

I have a ngb-accordion where the panels are generated through a for loop. I want to access a specific component/dom element inside the opening/closing panel and hide this element when the panel gets opened and show it when the panel collapses again.
I thought about using the panelChangeEvent because it gets called when the panel opens and collapses. I am now looking for the most elegant way to get the in the current panel. My first thought was using a ViewChildren QueryList for the NgbPanels, iterate through the list and get the changing panel.
How do I get the changing panel? I wanted to compare the panelID of the NgbPanelChange event to the id of the panel in the QueryList. Is there a way to get the id property? Because panel.id returns undefined (id is only an input in the NgbPanel component,correct?)
Afterwards get the instance inside the changing panel. But how do I get the reference to this specific instance? Am I thinking way too complicated here? I am kind of confused by the amount of possibilities. Appreciate any help.
Template
<ngb-accordion #acc="ngbAccordion" (panelChange)="beforeAccordionPanelChange($event)">...
<ngp-panel #accPanel [id]="panel-' + i" *ngFor="let panel of panels; let i= index" >
....
<target-component #target></target-component>
....
</ngp-panel>
</ngb-accordion>
TS
// Hide target component when panel is active
public beforeAccordionPanelChange($event) {
let changedPanelId = $event.panelId;
this.accordionPanelList.toArray().forEach(accordionPanel => {
if ( accordionPanel.id === changedPanelId ) {
// get target-component and call function toggleDisplay() which hides/shows the instance
}
});
}

Make a copy of DOM element then add animation

I read many articles and documentations about this but none of them seem to take the approach I'm trying to make.
I have an array of category Items and inside it I have a nested array of said category products.
like so.
<ng-container *ngFor="let menu of menuList">
<ng-container *ngFor="let item of menu.catItem" >
<div class="card" #cmp >
just to demonstrate
<button class="animateIt(cmp)"> click to animate </button>
</div>
</ng-container>
</ng-container>
when the user click the button I would like to add some kind of animation to, I can do that without any problem but the issue is I cannot think of a way to this without using any extra JavaScript libraries.
I have tried adding ViewChildren to my component. And a function that print the element
#ViewChildren('cmp') components: QueryList<ElementRef>;
constructor(private renderer: Renderer2){}
animateIt(cmp:ElementRef){
console.log(cmp);
this.renderer.setAttribute(cmp.nativeElement, 'class', 'myClass');
}
second line inside the function gives an error since cmp.nativeElement is not defined which is expected since all it does is to grab the content of the div and just print it without making an ElementRef object.
Is there any way I can achieve this using just angular or just JS?
I think you need to retrieve your element in ngAfterViewInit lifecycle, like below:
ngAfterViewInit() {
this.cmp.changes.subscribe(() => {
console.log('changed');
})
}
did it by using plain JS
here is the code
animateIt(cmp:ElementRef){
this.el.nativeElement = cmp;
let element = document.createElement('div');
element = this.el.nativeElement.cloneNode(true);
element.style.position = "absolute";
element.style.top = this.el.nativeElement.offsetTop + 'px';
element.style.left = this.el.nativeElement.offsetLeft + 'px';
let parent = document.getElementById("parent");
parent.appendChild(element);
}

Aurelia Jquery-UI Sortable communication

I'm trying to implement the sortable-list feature form jquery-ui with aurelia. Whats the best way to update the items (inside aurelia controller) with the new order from the dom? Is "ref" an approach here?
Easy re-sort the list-items in the dom, but how apply the changes to the aurelia-list-object?
<ul class="sortable">
<li repeat.for="item of items">
Stuff.
</li>
</ul>
One Approach would be to attach the $index to the li-item, read them with jquery after the order was changed and create an new array with the order ([0,3,1,2]). Then iterate throigh this array and push the items of the original "item"-array in the controller to a new array according to their index. This seems rather clunky and unperformant though.
Is there a more elegant solution?
We found that Aurelia doesn't really take kindly to "others" rearranging the DOM under its nose. The specific case that was broken for us (from memory) was when you drag the item around and return it to its original position.
What we did was attach an event handler for "sortstop" to the sortable component, and the event handler did this (this is Typescript) -- items is our list of items we have bound to, and refSortable is the sortable element:
onStop( event: JQueryEventObject, ui: any ): boolean
{
let endPos = ui.item.index( );
let startPos = ui.item.data( "start_pos" );
$( this.refSortable ).sortable( "cancel" );
let movedItem = this.items[ startPos ];
if( startPos === endPos )
{
// KLUDGE: even though the item has not moved, we need to create a new one to force Aurelia to rebind
let newItem = new ExportListItem( movedItem.id, movedItem.caption, movedItem.sourceObject );
movedItem = newItem;
}
this.items.splice( startPos, 1 );
this.items.splice( endPos, 0, movedItem );
// We end up with a duplicate DOM element that needs to be removed.
ui.item.remove( );
}
I won't say it's elegant by any stretch, but it gets Aurelia behaving correctly.

AngularJS on top of server generated content

I'm looking for a way to integrate something like ng-repeat with static content. That is, to send static divs and to have them bound to JS array (or rather, to have an array constructed from content and then bound to it).
I realize that I could send static content, then remove and regenerate the dynamic bits. I'd like not to write the same divs twice though.
The goal is not only to cater for search engines and people without js, but to strike a healthy balance between static websites and single page applications.
I'm not sure this is exactly what you meant, but it was interesting enough to try.
Basically what this directive does is create an item for each of its children by collecting the properties that were bound with ng-bind. And after it's done that it leaves just the first child as a template for ng-repeat.
Directive:
var app = angular.module('myApp', []);
app.directive('unrepeat', function($parse) {
return {
compile : function (element, attrs) {
/* get name of array and item from unrepeat-attribute */
var arrays = $parse(attrs.unrepeat)();
angular.forEach(arrays, function(v,i){
this[i] = [];
/* get items from divs */
angular.forEach(element.children(), function(el){
var item = {}
/* find the bound properties, and put text values on item */
$(el).find('[ng-bind^="'+v+'."]').each(function(){
var prop = $(this).attr('ng-bind').split('.');
/* ignoring for the moment complex properties like item.prop.subprop */
item[prop[1]] = $(this).text();
});
this[i].push(item);
});
});
/* remove all children except first */
$(element).children(':gt(0)').remove()
/* add array to scope in postLink, when we have a scope to add it to*/
return function postLink(scope) {
angular.forEach(arrays, function(v,i){
scope[i] = this[i];
});
}
}
};
});
Usage example:
<div ng-app="myApp" >
<div unrepeat="{list:'item'}" >
<div ng-repeat="item in list">
<span ng-bind="item.name">foo</span>
<span ng-bind="item.value">bar</span>
</div>
<div ng-repeat="item in list">
<span ng-bind="item.name">spam</span>
<span ng-bind="item.value">eggs</span>
</div>
<div ng-repeat="item in list">
<span ng-bind="item.name">cookies</span>
<span ng-bind="item.value">milk</span>
</div>
</div>
<button ng-click="list.push({name:'piep', value:'bla'})">Add</button>
</div>
Presumable those repeated divs are created in a loop by PHP or some other backend application, hence why I put ng-repeat in all of them.
http://jsfiddle.net/LvjyZ/
(Note that there is some superfluous use of $(), because I didn't load jQuery and Angular in the right order, and the .find on angular's jqLite lacks some features.)
You really have only one choice for this:
Render differently for search engines on the server, using something like the approach described here
The problem is you would need to basically rewrite all the directives to support loading their data from DOM, and then loading their templates somehow without having them show up in the DOM as well.
As an alternative, you could investigate using React instead of Angular, which (at least according to their website) could be used to render things directly on the web server without using a heavy setup like phantomjs.

How do I update my html on Click in Angularjs Controllers

I have the html Structure that I need to update from the json data. My Json data is in a Controller. I need to write an expression for ng-click event that will read the json data and put the in the corresponding div in html. but I am not sure how to acheive this.
Below is what I have so far.
<body data-ng-app>
<div class="container" data-ng-controller="UpdateDataCtrl">
<div class="inner1"></div>
<div class="inner2"></div>
</div>
UPdate Controllers
</body>
function UpdateDataCtrl($scope) {
$scope.data = [
{
"USA":"Eglish",
"Pop":"232423432432"
},
{
"France":"French",
"Pop":"1212323432"
},
{
"Spain":"Spainish",
"Pop":"3432432"
}
]
}
On each click the 2 Div should get updated from the json. First div should have USA---English Pop---2342234232 and then on next click the div should have data from France and so on.
http://jsfiddle.net/MBFpD/1/
Thanks
It appears that you are unclear on the concept of AngularjS. You don't want to update the DIVs. You want to reference your model and then change the data in your model.
For example you can write the div like this:
<div class="inner1">Population: {{data[dataindex].Pop}}</div>
Then in the Controller you initialize the dataindex to 0, so that this will output the population from the first entry in the array:
$scope.dataindex = 0;
The click function (you must have the link with the ng:click inside the block governed by the Controller!) could then just increase the dataindex by one and by using modulo restart at 0 again when the end of the array was reached.
$scope.click = function() {
$scope.dataindex = ($scope.dataindex+1) % $scope.data.length;
Here is an updated and modified jsfiddle of your example which will show everything in action: http://jsfiddle.net/MBFpD/2/
Bind your data to your scope when you click on the link:
$scope.update = function() {
$scope.data = data; //your array defined locally to the scope
};
ng-repeat your data bound to the scope; display the container if the size of the array is > 0.
Use {{index}} to get the iteration variable inside the loop.
Above all, move your ng-controller declarative at the top to enclose both your ng-repeat and your ng-click; otherwise, AngularJS cannot guess what you want to achieve.
http://jsfiddle.net/MBFpD/5/

Categories

Resources