How do I make a copy of the component in Angular? - javascript

I am using Angular 5 in my project. I did pagination with use ngx-pagination . I need to do this pagination top and bottom table. I need to make pagination at the top and bottom of the table. I did it this way
<div class="admin-panel__nav-block navigation-block">
<app-table-filter-size (valueChange)="valueChange($event)" [size]="size"></app-table-filter-size>
<pagination-controls class="table-pagination pagination-block" previousLabel="Previous" nextLabel="Next" id="listing_pagination" (pageChange)="pageChange(p = $event)"></pagination-controls>
</div>
<!--Table -->
...
<!--Table End -->
<div class="admin-panel__nav-block navigation-block">
<app-table-filter-size (valueChange)="valueChange($event)" [size]="size"></app-table-filter-size>
<pagination-controls class="table-pagination pagination-block" previousLabel="Previous" nextLabel="Next" id="listing_pagination" (pageChange)="pageChange(p = $event)"></pagination-controls>
</div>
it's working , but it's probably not a good practice, since the same id is used in different places. how can I get rid of duplicate code? What should I do in my situation?

You need to have an id to uniquely identify dynamically assigned instead of having hardcoded id="listing_pagination"
Have a paginationConfig Array and push configs for each set of data
ts file implementation
private paginationConfigs: Array; // define the variable
this.paginationConfigs = []; //initialize in the ngOnInit lifecycle function
Have the below configuration for each dataset probably inside the loop
const pagerConfig = {
id: `pager-${value}`, // value could be any unique identifer
itemsPerPage: 10,
currentPage: 1
};
this.paginationConfigs.push(pagerConfig);
html file implementation
<tr *ngFor="let item of mf.data| paginate: paginationConfigs[i]" >// data display </tr>
<pagination-controls previousLabel="Previous" nextLabel="Next [id]='paginationConfigs[i].id'(pageChange)="pageChange(paginationConfigs[i].currentPage= $event)"></pagination-controls>

Related

How can I succssefully acces an Global Variable from PHP in HTML

Pre Info:
In the PHP I declare a global array ($GLOBALS[]), in it I stow the data for the blade.
On the Blade i have a dropdown. If the dropdown changes, i send via AJAX the value to the Controller.
With the new data i calculate the new information, i want to show on the Blade.
But when I try to get the data in HTML like {{$GLOBALS['MyNumber']}} I only get the initialization value.
<?php
.
.
.
$GLOBALS = array(
'MyNumber' => 1,
'companyCode' => 0
);
class MyClass extends Controller
{
public function getData(){
$GLOBALS['companyCode'] = #$_POST['companyCode'];
if ($GLOBALS['companyCode'] == 902){
$GLOBALS['MyNumber'] = 100; //for the example, I set the data fix
}else{
$GLOBALS['MyNumber'] = 20; //for the example, I set the data fix
}
return response()->json(array('AJAX succses' => $response), 200);
}
.
.
.
In HTML i wanna show an dashboard-card with the new data like this:
I have 4 Cards and 7 Tables with data from SQL.
#section('page_content')
<div class="card #if($GLOBALS['MyNumber'] >95) dashboard-card-success #else card-anger-#endif" id="bkfI">
<div class="dashboard-title">
{{ __('bkf_usage') }} {{ today()->year }}
</div>
<div class="dashboard-content">
<div class="dashboard-number" id="bkf_usage">{{$GLOBALS['MyNumber'] }} %</div>
<div class="dashboard-description"></div>
</div>
</div>
But every time i reload the div (so the color can change) the $GLOBALS['MyNumber'] stays on the init of 1.
But the internal use (if $GLOBALS['companyCode']) works fine. How can i solve this issue? Or can you help me to find a work-around?
Maybe pass all the Vars back via ajax to JS and store tem in an JS variable? So i can still access from HTML to them?
Thank You!

Vue.js: How to map a list of keys to Firebase objects?

I develop a small web-app based on Vue.js using Firebase to store and sync the data. I store items (e.g. with attributes title and subtitle) and lists with an attribute listitems, where an array of keys (those generated from Firebase) of items is stored. The structure looks like this:
Now the problem: I want to display a list and show the items from the listitems attribute and I'm doing it like this:
Compontent:
var ShowList = Vue.extend({
template: '#show-list',
firebase: {
// get all existing items from firebase
items: firebase.database().ref('items')
},
data: function () {
// get list item keys of list 'list_id' and bind it to this.list
this.$bindAsObject('list', listsRef.child(this.$route.params.list_id));
return {
list: this.list
};
}
});
Template:
<!-- show a list -->
<template id="show-list">
<ul v-if="list.items != ''">
<li v-for="key in list.items"> <!-- I would like to not being forced to -->
<template v-for="item in items"> <!-- iterate the whole list of existing items -->
<span v-if="item['.key'] == key">
{{ item.title }}
</span>
</template>
</li>
</ul>
<div v-else>No items.</div>
</template>
As you can see, I have to use two iterations where I iterate the full items list for every entry in list.items.
My question: Is there a more efficient way to map the actual objects to the list of object keys? For a huge number of item records, my approach will be very slow. Maybe I'm just too blind to see a simpler solution?
Thanks for your time!
I think you have to denormalize/duplicate some data there. I had a similar situation and this Firebase video cleared a lot of things up for me: https://youtu.be/ran_Ylug7AE?t=2m22s (Link updated to passage at 2:22. The whole serie is worth watching btw.)
My shot at it would be adding (Firebase) keys in "listitems", just like you have done in "items", with only the most crucial data there, so that you can link to a full description
Is your data read only? In which case you could move the filter logic from your template to your data module, like so (I expect I have unintended side-effects):
data: function () {
// get list item keys of list 'list_id' and bind it to this.list
this.$bindAsObject('list', listsRef.child(this.$route.params.list_id));
var items = firebase.database().ref('items')
var activeItems = this.list.items.map(function(key) {
return items[key]
})
return {
activeItems: activeItems;
};
}

angular performance: critical rendering path?

Im trying to optimice the page loading speed when rendering tables with many rows(columns min. 25x).
I am not experienced debugging/improving performance on angular apps so quite lost on what could be involved in this lack of speed.
Here is Chrome timeline report for 5 row query:
Here is Chrome timeline report for 100 row query:
The XHR load(api/list/json/Chemical...) increases in time as more rows are rendered on the table.
The server response with the data is returned fast(not the bottle neck):
Here is the template for the table:
<tbody ng-if="compressed">
<tr ng-if="dbos && (rows.length == 0)">
<td class="tableColumnsDocs"><div class="tableButtons"> </div></td>
<td class="tableColumnsDocs"><div>No results</div></td>
<td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index" ng-if="$index > 0">
<p> </p>
</td>
</tr>
<tr class="tableRowsDocs" ng-repeat="dbo in rows track by $index">
<td class="tableColumnsDocs"><div ng-include="'link_as_eye_template'"></div></td>
<td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index">
<div ng-init="values = dbo.get4(attobj.key); key = attobj.key; template = attobj.template || getAttributeTemplate(dbo.clazz + attobj.key);">
<div class="content" ng-include="template"></div>
<div class="contentFiller" ng-include="template"></div>
</div>
</td>
</tr>
</tbody>
And here templates the table will call:
<script type="text/ng-template" id="plain_values_template">
<p ng-repeat="v in values track by $index">{{ v }}</p>
</script>
<script type="text/ng-template" id="links_as_dns_template">
<div ng-repeat="dbo in values track by $index" ng-include="'link_as_dn_template'"></div>
</script>
<script type="text/ng-template" id="json_doc_template">
<textarea class="form-control" rows="{{values.length + 2}}" ng-trim="false" ng-readonly="true">{{ values | json }}</textarea>
</script>
<script type="text/ng-template" id="link_as_dn_template">
<p>{{ dbo.displayName() }}</p>
Relevant controller part:
$scope.getAttributeTemplate = function(str) {
//console.log("getAttributeTemplate"); console.log(str);
if ($templateCache.get(str + ".template")) {
return str + ".template";
}
var a = str.split(/(>|<)/);
//console.log(a);
if ((a.length - 1) % 4 == 0) {
return "links_as_dns_template";
}
var clsname = a[a.length - 3];
if (clsname == "*") {
return "plain_values_template";
}
var attname = a[a.length - 1];
var cls = datamodel.classes[clsname];
var att = cls.attribute[attname];
if (!att) {
return "plain_values_template";
}
if (att.type == "ref") {
return "links_as_dns_template";
}
return "plain_values_template";
};
I am new to angular and performance opt. so any tips on how to improove or bad practice highlight will be very helpful!
Long tables are angular's biggest evil, because of the hell-as-slow base directives such as ng-repeat
Some easy and obvious stuffs :
I see a lot of bindings in the row/cell templates without one-time binding (::). I dont think your row data is mutating. switching to one-time bindings will reduce the watchers count -> perf.
Some harder stuff :
Quick answer :
dont let angular handle the performance bottleneck
Long answer :
ng-repeat is supposed to compile it's transcluded content once. But using ng-include is killing this effet, causing every row to call compile on their ng-included contents. The key for good performance in big table is to be able to generates (yea, manually, which $compile, $interpolate and stuff) a unique compiled row linking function, with less as possible angular directives - ideally only one-time expression bindings, and to handle row addiction/removal manually (no ng-repeat, you own directive, your own logic)
You should AT LEAST find a way to avoid the second nested ng-repeat on' ng-repeat="attobj in columns track by $index"'. This is a dual repeated on each row, killing compilation &linking (rendering perf) and watcher count (lifecycle perf)
EDIT : as asked, a "naive" example of what can be done to handle the table rendering as manually (and fast) as possible. Note that the example does not handle generating the table header, but it's usually not the hardest thing.
function myCustomRowCompiler(columns) {
var getCellTemplate = function(attribute) {
// this is tricky as i dont know what your "getAttributeTemplate" method does, but it should be able to return
// the cell template AS HTML -> you maybe would need to load them before, as getting them from your server is async.
// but for example, the naive example to display given attribute would be
return $('<span>').text("{{::model."+ attribute +"}}"); // this is NOT interpolated yet
};
var myRowTemplate = $('<tr class="tableRowsDocs">');
// we construct, column per column, the cells of the template row
_.each(columns, function(colAttribute, cellIdx) {
var cell = $("<td>");
cell.html(getCellTemplate());
cell.appendTo(myRowTemplate);
})
return $compile(myRowTemplate); // this returns the linking function
}
and the naive usage :
function renderTableRows(dbos, columns) {
var $scope; // this would be the scope of your TABLE directive
var tableElement = $el; // this would be your table CONTENT
var rowLinker = myCustomRowCompiler(columns); // note : in real life, you would compile this ONCE, but every time you add rows.
for(var i=0; i<dbos; i++) {
var rowScope = $scope.$new(); // creating a scope for each row
rowScope.model = dbos[0]; // injecting the data model to the row scope
rowLinker(rowScope, function(rowClone) { // note : you HAVE to use the linking function second parameter, else it will not clone the element and always use the template
rowClone.appendTo(tableElement);
});
}
};
This is the approach i've been using to my own projects's table framework (well, more advanced, but this is really the global idea), allowing to use angular power to render the cell content ( 'getCellTemplate' implementation can return html with directive, which will be compiled), using filter even including directives in the cell, but keeping the table rendering logic to myself, to avoid useless ng-repeat watch, and minimizing the compilation overheat to it's minimum.

Arrays concatenation in AngularJS and owl-carousel environment

I'm trying to load an carousel with angularjs using owl-carousel. I want my carousel to scroll endless, loading items every time the list is fully scrolled and adding queried elements to the actual list. My issue is:
When I get data from the controller of the next page, I want to merge and contact the received items to be merged the the current array and be rendered at the end of the carousel, here is what I've done:
<data-owl-carousel class="owl-carousel" data-options="{navigation: true, pagination: false, rewindNav : false}">
<div owl-carousel-item="" ng-repeat="item in hmc.ProductData.Products track by $index" class="item">
<a ng-href="/#!//{{Page.Culture+'/product/'+item.id}}">
<div class="telewebion-show-box one-row">
<div class="telewebion-show-box-cover">
<ul>
<li>{{::item.title}}</li>
<li>{{::item.price}}</li>
</ul>
</div>
<img ng-src="{{::item.picture_path}}" width="220" height="148" alt="" class="img-responsive"/>
</div>
</a>
</div>
</data-owl-carousel>
And here is my controller:
hmc.getProducts=function(){
ProductFactory.getProducts(hmc.ProductData.Offset,hmc.ProductData.Limit).then(function(Products){
if(hmc.ProductData.Page==0)
{
hmc.ProductData.Products[0]='';
}
hmc.ProductData.Page++;
var tempArray=[];
tempArray.push(Products);
console.log(tempArray);
hmc.ProductData.Products [0]=hmc.ProductData.Products [0].concat(tempArray[0]);
console.log(hmc.ProductData.Products );
hmc.ProductData.UpdateInProgress=false;
});
}
but it doesn't contact and merge the array and wouldn't work.
With tempArray.push(Products); you push a single array, considered as the whole variable, in a single cell of the array tempArray without obtaining the expected result. You should call:
tempArray = tempArray.concat(Products);
This way you push every element of Products in tempArray. Please note that concat does not modify the calling array directly, this behavior force you to reassign its return value to the original tempArray.
I Just did it like this:
hmc.ProductData.Page++;
var tempArray=[];
var tempArray2=[];
tempArray.push(AjaxProducts);
tempArray2.push(hmc.ProductData.Products);
hmc.ProductData.Products= tempArray2[0].concat(tempArray[0]);
tempArray.length = 0;
tempArray2.length = 0;
Now angular ng-repeat just repeat in a single array structure and ajax appends to the end of that array

Angular.js paginate filtered data and display total items without duplicating code in view/controller

Let's say I have a filter implemented in a view like this:
<input data-ng-model="statementFilter" />
<ul>
<li data-ng-repeat="statement in statements | filter: statementFilter">
{{ statement.Name }}
</li>
</ul>
This will do a case insensitive partial match for statements with any properties that contain statementFilter.
I need to implement this in my controller instead of in my view. I understand you can create custom filters in Angular, but I want my filter to do the generic case insensitive partial matching on any complex object that the built-in Angular filter in the view does. If I create a custom filter, I have to do the actually filtering using javascript which will require another library AFAIK.
How can I leverage the generic case insensitive partial matching that comes with Angular's "view" filter in code?
Thanks
UPDATE
Here is a Plunker of what I'm trying to do.
Solution: http://plnkr.co/edit/AcAP437OJGMgMuGCtxT3?p=preview
Basically, the problem was that the controller was doing what you knew could be accomplished inside of the view... what made this more difficult than most other cases was that you're trying to paginate the same data that you're filtering and displaying the length... so this means that your data must be manipulated in this order:
filtered data based on search item
capture the length of the filtered items
paginate the filtered items and display
The first thing that I know had to be done was rework the ng-repeat to do the filtering. The goal was to use that build in angular filtering.
Originally, it looked like this. Which did the filtering AND the paging, but used custom code in the controller.
data-ng-repeat="statement in pagedStatementData()"
Step 1: Use the angular filter
The filtering that you posted in your question was an easier way to do this without writing custom filtering code... so that was my first step. Easy enough.
data-ng-repeat="statement in statements | filter:statementFilter"
Step 2: Get pagination back
At this point, the list is filtered correctly, but displays all of the filtered items and does not break them into pages. The pagination buttons work as they should and the total records update accordingly. So now the next step is to insert that pagination into this filtered list.
In the script, I added begin and end to the scope. These variables were previously created inside of the pagedStatementData(). Then using those values, I can slice the filtered array to get the pagination going.
Note: This $scope.begin $scope.end code was eventually removed in Step 5, because it's only calculated on the initial render and didn't update after then. It was a bug I didn't notice until Step 5.
$scope.begin = ($scope.currentPage-1)*$scope.numPerPage;
$scope.end = ($scope.begin + $scope.numPerPage);
data-ng-repeat="statement in (statements | filter:statementFilter).slice(begin, end)"
Step 3: Remove controller code that is not wanted/needed
At this point, everything works... but the goal is to remove the custom filtering code... so I removed the $scope.filteredStatementData method and the $scope.totalFilteredStatementItems method that called it. $scope.pagedStatementData can get deleted also.. that was called in the ng-repeat that was modified in Step 1.
Removed:
$scope.filteredStatementData // custom filter code.. removed
$scope.totalFilteredStatementItems // called filteredStatementData... removed
$scope.pagedStatementData // this was called by the original ng-repeat... removed
Step 4: Fix total items # and pagination buttons. Both depend on the same .length
At this point... the view is broken, because it's still making a few calls to the methods we just removed. (totalFilteredStatementItems) So now the goal is to replace that functionality with what we have in the view. totalFilteredStatementItems used to run that custom filtering logic and then got the length without paginating the data.
We already have the items being filtered, so we just need to save them to the scope (before they're paginated) so that they can be accessed elsewhere. We can save that filtered array inside of the ng-repeat, actually. As long as the syntax remains item in items... but items can be assigned to a scope variable... like item in (items = (/*filter*/)).slice(x,y)
data-ng-repeat="statement in (filteredItems = (statements | filter:statementFilter)).slice(being, end)"
<div>Total records: {{ filteredItems.length }}</div>
<pagination data-ng-model="currentPage" total-items="filteredItems.length"
Okay. That ng-repeat is starting to get crazy, but it's still working. The parens are the real magic here. This code is executed in the desired order.
// filtered data based on search item
$scope.filteredItems = $scope.statements.filter(/*statementFilter magic*/);
// paginate the filtered items
var _temp = filteredItems.slice($scope.begin, $scope.end),
_i, statement;
// display page of filtered items
for (var _i in _temp) {
statement = _temp[_i];
// Render each row w/ statement
}
Also, I'm sure there's some Angular $scope magic going on to update the filteredItems.length since it's used in the Total records: div before the list is filtered... thanks Angular! Or maybe it prioritizes ng-repeat and executes that block first. Idk. It works.
Step 5: Pagination is broken. Get the pagination component to update begin and end variables that the list depends on.
Deleted $scope.begin and $scope.end code in controller.
Create them inside of ng-init when the component is first created, and then on the data-ng-change event, recalculate those values.
<pagination data-ng-model="currentPage" total-items="filteredItems.length"
items-per-page="numPerPage" data-max-size="maxSize" data-boundary-links="true"
ng-init="begin = (currentPage-1)*numPerPage; end = begin + numPerPage"
data-ng-change="begin = (currentPage-1)*numPerPage; end = begin + numPerPage">
Here's another approach achieving the same thing with multiple filters and pagination. There's not really any advantages over Cory's answer except this version does not depend on angular-ui for the pagination controls.
app.js
var app = angular.module('statementApp', ['ui.bootstrap']);
app.controller('statementController', function($scope, $interpolate) {
$scope.currentPage = 0;
$scope.pageSize = 10;
$scope.data = [
{name:"John Smith", price:"1.20"},
{name:"John Smith", price:"1.20"},
{name:"Sam Smith", price:"1.20"},
{name:"Sam Smith", price:"1.20"},
{name:"Sam Smith", price:"1.20"},
{name:"Sam Smith", price:"1.20"},
{name:"Sarah Smith", price:"1.20"},
{name:"Sarah Smith", price:"1.20"},
{name:"Sarah Smith", price:"1.20"},
{name:"Sarah Smith", price:"1.20"},
{name:"Sarah Smith", price:"1.20"},
{name:"Sarah Smith", price:"1.20"}
];
var init = function() {
$scope.initPaging();
}
$scope.searchFilter = function(item) {
if(!$scope.searchVal) return true;
var qsRegex = new RegExp($scope.searchVal, 'gi');
return qsRegex ? item.name.match(qsRegex) : true;
}
$scope.initPaging = function() {
$scope.currentPage = 0;
$scope.numberOfPages=function(){
if($scope.filteredItems.length > $scope.pageSize) {
return Math.ceil($scope.filteredItems.length / $scope.pageSize);
}
return 1;
}
}
$scope.$watch(function () {
$scope.filteredItems = $scope.$eval("data | filter:searchFilter | orderBy: 'price'");
});
init();
});
index.html
<!DOCTYPE html>
<html ng-app="statementApp">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.12.0.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="script.js"></script>
</head>
<body ng-controller="statementController">
<input id="txtStatementFilter" class="form-control" ng-model="searchVal" placeholder="Search records" />
<div>Total records: {{ filteredItems.length }}</div>
<table class="table table-hover table-bordered table-striped table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in (filteredItems).slice(currentPage*pageSize,(currentPage*pageSize) + pageSize)">
<td>{{item.name}}</td>
<td>{{item.price | currency:"£":0}}</td>
</tr>
</tbody>
</table>
<button ng-disabled="currentPage == 0" ng-click="currentPage=currentPage-1">
Previous
</button>
{{currentPage+1}}/{{numberOfPages()}}
<button ng-disabled="currentPage >= filteredItems.length/pageSize - 1" ng-click="currentPage=currentPage+1">
Next
</button>
</body>
</html>
Plunker link http://plnkr.co/edit/OWotCiVYBed77F50wdMf?p=preview

Categories

Resources