I need to read the value of the lastPage property (which exists in the custom directove below) from inside my controller.
https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination
function generatePagination() {
if (paginationService.isRegistered(paginationId)) {
var page = parseInt(paginationService.getCurrentPage(paginationId)) || 1;
scope.pages = generatePagesArray(page, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
scope.pagination.current = page;
scope.pagination.last = scope.pages[scope.pages.length - 1];
// ** here is the code I added that I would like to work **
parent.scope.lastPage = scope.pagination.last;
if (scope.pagination.last < scope.pagination.current) {
scope.setCurrent(scope.pagination.last);
} else {
updateRangeValues();
}
}
}
This is the author's function above in the directive. It has a property called: scope.pagination.last that I would like to access from my controller. I tried to do add something like this line:
parent.scope.lastPage = scope.pagination.last;
but that does not work, or I do not know how to inject parent, etc.
As Ori Price points out, this is not a best practice as it will make this directive dependent on the parent scope ... but maybe you already know that and just need a solution - in that case. $rootScope is what you're looking for.
3 things you need to do:
1.) add it as a dependency to the directive.
.directive('dirPaginationControls', ['paginationService', 'paginationTemplate','$rootScope', dirPaginationControlsDirective])
2.) actually pass it into the directive function:
function dirPaginationControlsDirective(paginationService, paginationTemplate, $rootScope) { ...
3.) Use it wherever you want in the directive... for you it would look something like this:
function generatePagination() {
if (paginationService.isRegistered(paginationId)) {
var page = parseInt(paginationService.getCurrentPage(paginationId)) || 1;
scope.pages = generatePagesArray(page, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
scope.pagination.current = page;
scope.pagination.last = scope.pages[scope.pages.length - 1];
$rootScope.lastPage = scope.pagination.last;
if (scope.pagination.last < scope.pagination.current) {
scope.setCurrent(scope.pagination.last);
} else {
updateRangeValues();
}
}
}
You may need to do $rootScope.$parent.lastPage = scope.pagination.last I can't remember off the top of my head so do a console.log($rootScope) just to look at all of the options you have access to.
Related
In view I have 4 buttons. In controller I have references to those buttons. There is the complicated logic to enable/disable each button, unfortunatelly scattered into several function - event listeners. Each listener has different parameters. Example:
listener1: function(data1) {
button1.setDisabled(data1.status = ProcessStatus.running);
button2.setDisabled(data1.status = ProcessStatus.running);
}
listener2: function(upload) {
button2.setDisabled(!upload.enabled);
}
I want to refactor the code: create single function in controller updateButtonsState. Then each listener would set somewhere a value and call updateButtonsState() without parameters.
listener2: function(upload) {
saveUploadEnabled(upload.enabled);
updateButtonsState();
button2.setDisabled(!upload.enabled);
}
updateButtonsState: function() {
var uploadEnabled = loadUploadEnabled();
var data1StatusProcessRunning = loadData1Status();
button1.setDisabled(data1StatusProcessRunning);
button2.setDisabled(uploadEnabled || data1StatusProcessRunning);
}
How to implement save* and load* methods? Where should I store those data?
Two major possibilities: You can store the state directly in the controller, or use a closure to create a "private" variable and not expose it to anything except the getter/setter functions that are your interface.
controllers have an init method that you could use for the closure:
init: function(application) {
var myPrivateVariable = {};
this.saveUploadEnabled = function(uploadEnabled) {
myPrivateVariable.uploadEnabled = uploadEnabled;
}
this.saveStatus = function(status) {
myPrivateVariable.status = status;
}
this.updateButtonState = function() {
button1.setDisabled(myPrivateVariable.status == ProcessStatus.Running || !myPrivateVariable.uploadEnabled)
}
}
I have an AngularJS Service defined as follows:
function testService(testProvider) {
var ref = this;
ref.firstLevel = {};
ref.secondLevel = {};
initialize();
function initialize() {
testProvider.getData().then(function(result) {
ref.firstLevel = result;
ref.secondLevel.testData = result;
});
}
}
The testProvider is a simple wrapper around $http.get that fetches data from a JSON. The controller copies over these properties:
function testController(testService) {
var vm = this;
vm.firstLevel = testService.firstLevel;
vm.secondLevel = testService.secondLevel;
}
When I create bindings in my template, the second level works, the first level doesn't.
<!-- Doesn't work -->
<p>{{vm.firstLevel.testProperty1}}</p>
<p>{{vm.firstLevel.testProperty2}}</p>
<!-- Does work -->
<p>{{vm.secondLevel.testData.testProperty1}}</p>
<p>{{vm.secondLevel.testData.testProperty2}}</p>
See this Plunker for a working example:
https://plnkr.co/edit/pLInqcaJNhhbQWbvTUEE
Why doesn't the first level example work?
This is because when you overwrite an object in Javascript, you actually lose the reference to the actual object.
testProvider.getData().then(function(result) {
ref.firstLevel = result;
ref.secondLevel.testData = result;
});
Here ref.firstLevel = result overwrites the reference to the object that was initialized to {}. Any data bindings that you had on that object would be lost after this line.
Whereas, by doing ref.secondLevel.testData = result, you are not rewriting the object, rather you are only modifying the object by adding an extra key testData. Thus the reference is still preserved and so are the AngularJS bindings.
See this answer for more clarity.
I am trying to implement two functions in an an angular app but as soon as I implement the filter (start with letters from to), the code stops working. On their own, the (add/delete) functions work but as soon as I turn the data into a factory and try to access with the filter functions it fails.
Working functions:
$scope.items = items;
$scope.deleteItem = function (index) {
items.data.splice(index, 1);
}
$scope.addItem = function (index) {
items.data.push({
Name: $scope.newItemName
});
}
What causes the whole thing to break:
//filtering letters _ NOT WORKING
function setLetters (from, to){
this.fromLetter = from;
this.toLetter = to;
}
//----
$scope.filter.startsWithLetter = function () {
return function (items, fromLetter, toLetter) {
var filtered = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
var firstLetter = item.Name.substring(0, 1).toLowerCase();
if ((!fromLetter || firstLetter >= fromLetter)
&& (!toLetter || firstLetter <= toLetter)) {
filtered.push(item);
}
}
return filtered;
};
});
//--filtering letters
Full code here: fiddle
There's a few issues in the fiddle. First I'm seeing an "Unexpected token )" error due to an extra ) on line 58.
Then when I fix that there is an issue on line 45 as you are trying to assign a value to $scope.filter.startsWithLetter, when $scope.filter is undefined. I think you want to assign the value to $scope.startsWithLetter.
There is still a problem with the filtering. When filtering with ng-repeat you can specify a filter or simply a predicate function. In each case the arguments passed to the function will be different - please read the docs. The function as-is is designed to be used in a filter created with angular.module('myApp', []).filter(). It doesn't work when you set it on the scope and pass it to filter: as a predicate function. If you prefer to filter using a function on the scope, rather than creating a reusable custom filter, you need to change it to accept the correct arguments - see fiddle.
Your page is trying to access setLetters in $scope.items.data but you are not setting $scope.items.data.setLetters. I don't think it makes sense to set it there inside items.data anyway. Perhaps set it directly on the scope? I also would set fromLetter and toLetter directly on the scope.
I also moved the setLetter buttons inside a <div ng-controller="ItemsController" >
Fiddle with those fixes
I don't really understand why the following code isn't working. I want iron-media-query to call a function when the query changes. I got it working using query-matches and template if's but that isn't what I want at all. Here is the code I have:
<iron-media-query
query="(max-width:1024px)"
query-matches="{{condensedscreen}}"
query-changed="switchToCondensed">
</iron-media-query>
I want the query-change="switchToCondensed" to be called once this query requirement is met.
The switchedToCondensed function is the following:
switchToCondensed: function(e) {
console.log("Condensed: "+this.condensedscreen);
if(this.condensedscreen === true) {
this.sectionbox = "section-boxes-condensed";
this.setScreenSize = "checkoutBodyMobile";
//this.sectionStyle = "sectionMobile";
this.shippingSectionStyle = "shippingSectionMobile";
this.allCardsStyle = "allCardsMobile";
this.submitBtnStyle = "submitBtnMobile";
this.gotocartStyle = "goToCartMobile";
}
else {
this.sectionbox = "section-boxes";
this.setScreenSize = "checkoutBody";
//this.sectionStyle = "section";
this.shippingSectionStyle = "shippingSection";
this.allCardsStyle = "allCards";
this.submitBtnStyle = "submitBtn";
this.gotocartStyle = "goToCart";
}
}
As you can see I want to use this to change around the CSS on my webpage. What exactly am I doing wrong here?
Did you try to use "on-query-matches-changed" instead of "query-changed"?
<iron-media-query
query="(max-width:1024px)"
query-matches="{{condensedscreen}}"
on-query-matches-changed="switchToCondensed">
</iron-media-query>
So I never got query-changed to work. Honestly that seems completely useless. Anyway, I got it to work by first adding the $ to my class like the following:
<div class$="[[allCardsStyle]]">
After that I removed the query-changed from iron-media-query so it looked like the following:
<iron-media-query
query="(max-width:1024px)"
query-matches="{{condensedscreen}}">
</iron-media-query>
Then back in the JS I added an observer to watch when the boolean variable changes:
condensedscreen:{observer:"switchToCondensed"}
Finally, with that watching the change I had it call my function "switchToCondensed" that actually set the variable to mobile vs not:
switchToCondensed: function() {
if(this.condensedscreen === true) {
this.allCardsStyle = "allCardsMobile";
}
else {
this.allCardsStyle = "allCards";
}
}
Then in my CSS I have two different styles that are call allCards and allCardsMobile.
Hopefully this helps someone that was struggling with this like me.
Is there a way to force angular to re-render things bound to a property, even though the property has not changed value? e.g:
$scope.size = 1;
<div some-prop="size"></div>
$scope.$needsRender('size') // psuedocode
$scope.$apply(); // re-renders the <div>
Unfortunately I can't manage the property entirely in angular for performance reasons, which is why I need this "reset".
Angular DOM beeing manipulated outside of angular is ugly. Well to say the truth, it's more than that. People doing this probably deserves to die. slowly. Painfully.
But anyway yes, it is possible.
Short answer : You can do this be forcing the execution of a scope's watcher to fire.
module.factory("scopeUtils", function($parse) {
var scopeUtils = {
/**
* Apply watchers of given scope even if a digest progress is already in process on another level.
* This will only do a one-time cycle of watchers, without cascade digest.
*
* Please note that this is (almost) a hack, behaviour may be hazardous so please use with caution.
*
* #param {Scope} scope : scope to apply watchers from.
*/
applyWatchers : function(scope) {
scopeUtils.traverseScopeTree(scope, function(scope) {
var watchers = scope.$$watchers;
if(!watchers) {
return;
}
var watcher;
for(var i=0; i<watchers.length; i++) {
watcher = watchers[i];
var value = watcher.get(scope);
watcher.fn(value, value, scope);
}
});
},
traverseScopeTree : function(parentScope, traverseFn) {
var next,
current = parentScope,
target = parentScope;
do {
traverseFn(current);
if (!(next = (current.$$childHead ||
(current !== target && current.$$nextSibling)))) {
while(current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while((current = next));
}
};
return scopeUtils;
});
Use it simply like that :
scopeUtils.applyWatchers(myScope);