I have a page which lists numerous types of 'tiles' (<div>'s) in a main content area and a header which has a list of links which acts as filters.
E.g. In the header area, click the 'pdf' filter link - only tiles which have ng-show="showFiles['pdf'] will be shown. If 'video' is clicked, tiles with ng-show="showFiles['video'] will show and so on..
The header template is controlled by hdrController and the tiles by pageController.
Initially, when the view loads the $scope variable showFiles in pageController receives an object from a service Tweaks which sets all items to true (thus showing all tiles at start up):
testApp.controller('pageController', ['$scope', 'Tweaks', function($scope, Tweaks){
$scope.showFiles = Tweaks.tagFilters('all');
}]);
testApp.factory('Tweaks', function(){
var tweaksFactory = {};
var obj = {};
tweaksFactory.tagFilters = function(filter) {
if(filter == 'all') {
obj = {
'video' : true,
'pdf' : true,
'doc' : true
};
} else {
obj = {
'video' : false,
'pdf' : false,
'doc' : false
};
}
return obj;
};
return tweaksFactory;
});
Question: When clicking the filter links, a directive is applied which detects clicks - this then needs to update $scope.showFiles to show only tiles which are of the specific filter type.
See Plunkr - The $scope of pageController which contains the showFiles object doesn't update, so the changes aren't reflected.
Can someone offer any suggestions? I'm new to Angular - is this approach the best way to achieve the result?
You allways create a new 'obj' - so the reference in the controller won't be updated. Anyway you should always access the data/status through service functions. plnkr
testApp.factory('Tweaks', function(){
var tweaksFactory = {};
var obj = {};
tweaksFactory.tagFilters = function(filter) {
if(filter == 'all') {
obj = {
'video' : true,
'pdf' : true,
'doc' : true
};
} else {
obj = {
'video' : false,
'pdf' : false,
'doc' : false
};
obj[filter] = true;
}
console.log('alter the object - so it reflects in the scope');
console.log(obj);
return obj;
};
tweaksFactory.show = function(type) {
console.log(obj, type);
return obj[type];
};
return tweaksFactory;
});
communication with two or more controllers is done with services and events, you can do it with adding new service for broadcasting messages
testApp.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.prepForBroadcast = function(msg) {
$rootScope.$broadcast('handleBroadcast', msg);
};
return sharedService;
});
You can see your code updated using this service to allow directive to communicate with controller:
http://plnkr.co/edit/M3RECJmZa64cxKtpWeHO?p=info
Related
I Made a factory that keeps the information in my scopes in a series of 6 pages. Now when the user completes the 6th page and pushes the object I want the factory to reset to empty arrays again.
I already tried a lot with the timeout and apply elements, also tried a lot of combinations to set the array to empty (null, "", {}). but it still loads the old information when I load the page again(page 1/6).
The submit function (That also needs to reset the scopes) is
$scope.send = function(){
if(ArrayInfo.Checkmark == true){
firebase.database().ref('lorem/' + ArrayInfo.Ordernumber).set({
info: ArrayInfo,
Dates: Dates,
gasmeter: gasmeter,
gasmeter1: gasmeter1
}).then(function(){
firebase.database().ref('lorem2/' + ArrayInfo.currentYear).set({
last_number: ArrayInfo.Ordervalue
});
}).then(function(){
//ArrayInfo = {};
setTimeout(function(){
ArrayInfo = "";
$scope.info = "";
$scope.$apply();
$scope.$digest();
}, 50);
});
//close newrental
setTimeout(function(){
if (window.confirm('Information saved! You are ready to leave this screen? no changes possible after this point'))
{
//disable back button in home
$ionicHistory.nextViewOptions({
disableBack: true
});
//go home
$state.go("app.auth");
}
//error close newrental
else
{
alert("Take your time");
}
}, 50);
}
//error send array
else {
alert("Please accept the terms and conditions.");
}
}
My factory looks like this
mainapp.factory("infoFactory", function(){
ArrayInfo = {};
placeholders = {
"licenseone" : "img/placeholder.png",
"licensetwo" : "img/placeholder.png",
"licensethree" : "img/placeholder.png",
"licensefour" : "img/placeholder.png",
"imageone" : "img/front.png",
"imagetwo" : "img/sideleft.png",
"imagethree" : "img/back.png",
"imagefour" : "img/sideright.png",
"imagefive" : "img/roof.png",
"imagesix" : "img/placeholder.png",
"imageseven" : "img/placeholder.png",
"imageeight" : "img/placeholder.png"
};
gasmeter = {
"url" : "img/gas/gas1.png",
"gasvalue" : "1"
}
gasmeter1 = {
"url" : "img/gas/gas1.png",
"gasvalue" : "1"
}
ArrayInfo.returned = false;
RawDate = {};
Dates = {};
console.log(ArrayInfo);
return ArrayInfo;
return gasmeter;
return gasmeter1;
return placeholders;
return RawDate;
return Dates;
})
and I load the information in my controller like this
$scope.info = infoFactory;
$scope.Dates = Dates;
$scope.RawDate = RawDate;
$scope.gasmeter = gasmeter;
$scope.gasmeter1 = gasmeter1;
The angular version I am using is "3.6.6"
First of all, when you put return in your code, there's no use to include additional code after that, because it will never run. You need to return an Object instead.
mainapp.factory("infoFactory", function(){
ArrayInfo = {};
placeholders = {
"licenseone" : "img/placeholder.png",
// Rest of the images
};
gasmeter = {
"url" : "img/gas/gas1.png",
"gasvalue" : "1"
}
gasmeter1 = {
"url" : "img/gas/gas1.png",
"gasvalue" : "1"
}
ArrayInfo.returned = false;
RawDate = {};
Dates = {};
console.log(ArrayInfo);
return {
ArrayInfo: ArrayInfo,
gasmeter: gasmeter,
gasmeter1: gasmeter1,
placeholders: placeholders,
RawDate: RawDate,
Dates: Dates
};
})
Now you can inject infoFactory to the controller and use it like this: infoFactory.RawDate.
Now, if you want to reset the factory, you can add a function that reset all the data:
mainapp.factory("infoFactory", function(){
// Save a reference to the current pointer of the factory, so you won't loose it inside other scopes
var self = this;
self.params = {
ArrayInfo: {},
placeholders: {},
gasmeter: {},
gasmeter1: {},
ArrayInfo: false,
RawDate: {},
Dates: {}
};
self.reset = function() {
self.params.ArrayInfo = {};
self.params.placeholders.licenseone = "img/placeholder.png";
self.params.gasmeter.url = "img/gas/gas1.png";
self.params.gasmeter.gasvalue = "1";
self.params.gasmeter1.url = "img/gas/gas1.png";
self.params.gasmeter1.gasvalue = "1";
self.params.ArrayInfo.returned = false;
self.params.RawDate = {};
self.params.Dates = {};
}
self.reset(); // Call this function by default in order to initially set the factory properties
return {
reset: self.reset, // You can export the reset function and use it outside the factory too
ArrayInfo: self.params.ArrayInfo,
gasmeter: self.params.gasmeter,
gasmeter1: self.params.gasmeter1,
placeholders: self.params.placeholders,
RawDate: self.params.RawDate,
Dates: self.params.Dates
};
})
Now when you have a reset function, you can use it like this outside the factory: infoFactory.reset() whenever you want to reset the data to the initial state. I created inside the factory a base object (this.params = { .. }) and saved inside it all the details properties, inside the reset function I have updated those properties without breaking the original references (Working example).
The above is just an example, but you can (or perhaps should) encapsulate the params of the factory, and only allow the user to control and change the values via helper functions. Example of how to do it:
mainapp.factory("infoFactory", function(){
var self = this;
self.params = {
returned: false,
};
return {
setReturned: function(val) { self.params.returned = val === true; },
returned: function() { return self.params.returned; }
}
});
The above example will hide the actual params.returned from the user outside the factory, and only allow it to set the returned flag via helper function, i.e infoFactory.setReturned( true ); or infoFactory.setReturned( false );, and inside that setReturned function you can implement complex logic to validate the value sent to the function. Note that infoFactory.setReturned( 'invalid value!!!' ); will set the returned flag to false since i'm validating the value using the strict === operator - val === true.
Then, to get the value from the factory you call the infoFactory.returned() function - By using a function you're blocking outside access to the properties of the factory.
As a side note - Don't use setTimeout(function(){ ... }); Use $timeout and $interval and then you won't need $scope.$apply(); + $scope.$digest(); in order to manually run a digest cycle because it is being handeled nativaly by Angularjs for you
I've problem with AngularJS. Ng-repeat dosn't want to refresh loop when I add new item to JSON from another instance of controller
In first controller I set JSON
(function(){
'use strict';
angular.module('mainApp').controller('ListController', function($scope, FavoriteListService) {
$scope.addToFavorites = function(speaker){
FavoriteListService.setFavorites(speaker);
};
})();
In secound controller I have to display ng-repeat
(function(){
'use strict';
angular.module('mainApp').controller('ShowController', function($scope, FavoriteListService) {
$scope.favoritesList = FavoriteListService.getFavorites();
})();
Factory
(function () {
'use strict';
angular.module('mainApp').factory('FavoriteListService', function () {
var obj = {};
obj.getFavorites = function () {
var favorites = localStorage.getItem('speaker-favorites');
if (favorites == null) {
favorites = {};
} else {
favorites = JSON.parse(favorites);
}
return favorites;
};
obj.setFavorites = function (speaker) {
var favorites = obj.getFavorites();
favorites[speaker.uid] = {firstname: speaker.firstname, name: speaker.name};
localStorage.setItem('speaker-favorites', JSON.stringify(favorites));
};
return obj;
});
})();
Template:
<ul>
<li ng-repeat="(key, fav) in favoritesList">
{{fav.firstname}} {{fav.name}}
</li>
</ul>
Everything is fine, when set & display is in one controller.
If I want to use 2 controllers (or 2 instance of 1 controller) ng-repeat show correctly all items after load page, but when I add new item it doesn't refresh loop and doesn't show new item.
Is any method to fix it?
You either need to change repeater to (and assign that FavoriteListService to $scope variable):
ng-repeat="(key, fav) in FavoriteListService.getFavorites()"
or $watch that favorite list in your controller like that:
$scope.$watch(
function() { return FavoriteListService.getFavorites(); },
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
$scope.favoritesList = newValue;
}
},
true
);
Because when you assign your service method return to scope method it's not being working like a reference.
Create an empty object, and use angular.copy to update the object:
app.factory('FavoriteListService', function () {
var obj = {};
//create empty object
var favorites = {};
obj.getFavorites = function () {
let update = localStorage.getItem('speaker-favorites');
if (update) {
//Use angular copy
angular.copy(update, favorites);
};
return favorites;
};
obj.setFavorites = function (speaker) {
favorites[speaker.uid] = {firstname: speaker.firstname, name: speaker.name};
localStorage.setItem('speaker-favorites', JSON.stringify(favorites));
};
return obj;
});
By using angular.copy, all of the controllers that use the getFavorites function get the same object reference and all of them see the same changes to its contents.
For more information, see AngularJS angular.copy API Reference
I have a really serious problem, I'm updating, editing, deleting data, and the two-way data binding is not working.
This is one of my controllers:
'use strict';
var EventController = function($timeout, $scope, $state, EventModel) {
this.$timeout = $timeout;
this.$scope = $scope;
this.$state = $state;
this.EventModel = EventModel;
/**
* When the page is requested, retrieve all the data.
*
*/
this.retrieve();
};
EventController.prototype = {
create: function(event) {
var that = this;
this.EventModel.Model.insert(event)
.then(function() {
that.refresh();
});
},
retrieve: function() {
var that = this;
this.EventModel.Model.find()
.then(function(result) {
that.$scope.events = result;
});
},
one: function(id) {
var that = this;
this.EventModel.Model.one(id)
.then(function(result) {
that.$scope.event = result;
});
},
update: function(id, event, state) {
if (state !== undefined) {
event.is_active = state;
}
var that = this;
this.EventModel.Model.update(id, event)
.then(function() {
that.refresh();
});
},
delete: function(id) {
var check = $('[data-controller-input]:checked');
var that = this;
$.each(check, function() {
var target = $(this);
var id = target.prop('id');
that.EventModel.Model.remove(id)
.then(function() {
that.refresh();
});
});
},
clear: function() {
this.$scope.event = angular.copy(this.$scope.initial);
},
refresh: function() {
this.$state.go(this.$state.current, {}, {reload: true});
}
};
angular
.module('adminApp')
.controller('EventController',
[
'$timeout',
'$scope',
'$state',
'EventModel',
EventController
]
);
In the create, update and delete methods I need to update the HTML without refreshing the page, I already tried using, $scope.apply, $scope.digest, $timeout after the result came, but not happens in the HTML.
If I try $scope.apply and $scope.digest the error will be:
Prevent error $digest already in progress when calling $scope.$apply()
So I was trying to wrap the $scope.$apply or $digest with the $timeout, same result, nothing happens.
Thanks.
First of all, your refresh method will never update your controller.it will simply fail just because this.$state.current won't be able to resolve any url ,template or controller.
And this is the main reason you are not able to see updated data ,just check your console you might be getting Error: Cannot transition to abstract state '[object Object]' error.
Update : I have create a plnkr.as i don't have access to event model code i simply removed it and try to create the same scenario.
http://plnkr.co/edit/RsI3TgKwcjGEXcTMKoQR?p=preview
see if this can help you
I am not sure, but try using the following function which checks the current phase before executing your function. It may solve the issue.
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
Usage:
$scope.safeApply(function() {
//Your lines
});
Here goes my View model, which helps to load the items to drop down. Items are getting loaded but when I inspect the element "value" attribute is empty. How can I get selected value?
$(function () {
tss.Department = function (selectedItem) {
var self = this;
self.id = ko.observable();
self.description = ko.observable();
self.isSelected = ko.computed(function () {
return selectedItem() === self;
});
self.stateHasChanged = ko.observable(false);
};
tss.vm = (function () {
var metadata = {
pageTitle: "My App"
},
selectedDepartment = ko.observable(),
departments = ko.observableArray([]),
sortFunction = function (a, b) {
return a.description().toLowerCase() > b.description().toLowerCase() ? 1 : -1;
},
selectDepartment = function (p) {
selectedDepartment(p);
},
loadDepartments = function () {
tss.departmentDataService.getDepartments(tss.vm.loadDepartmentsCallback);
},
loadDepartmentsCallback = function (json) {
$.each(json, function (i, p) {
departments.push(new tss.Department(selectedDepartment)
.id(p.DepartmentId)
.description(p.Description)
);
});
departments.sort(sortFunction);
};
return {
metadata: metadata,
departments: departments,
selectDepartment: selectDepartment,
loadDepartmentsCallback: loadDepartmentsCallback,
loadDepartments: loadDepartments,
choices: choices,
selectedChoice: selectedChoice
};
})();
tss.vm.loadDepartments();
ko.applyBindings(tss.vm);
});
Here is my HTML
<select data-bind="options:departments, value:selectDepartment,
optionsText: 'description', optionsCaption:'Select a product ...'">
</select>
Also sorting is not happening. departmentDataService used to call external data. which has both "id" and "description"
I also tried setting value as 'Id', but did not work.
You should not use an additional function selectDepartment to pass the value to the observable, but instead directly bind the observable to the value property of the select-box:
<select data-bind="options:departments, value:selectedDepartment, ...
(remember to export the selectedDepartment observable)
The value property is not only used to communicate the current value from view to viewmodel, but also vice versa: to set the selected option. Binding to a function that provides only "write" functionality is therefore not sufficient.
If you need to react to changes of the selected department, you can subscribe to the observable (this is explained in the official docs).
For example,
I have the page where KO has been already registered and there is a viewmodel with observable property "someProperty";
I check that the "someProperty" is observable property by ko.isObservable(viewmodel.someProperty) - it returns 'true';
I do the ajax call to get some html markup where KO is registered too;
Now If you check the ko.isObservable(viewmodel.someProperty) it will return false;
Also all KO extensions which has been added manually will be lost. It looks like bug (or feature) in jQuery (http://bugs.jquery.com/ticket/10066).
var viewModel = new function() {
var self = this;
this.serverData = {
Controller: ko.observable(null),
Enabled: ko.observable(false),
Id: ko.observable(null),
ParentId: ko.observable(null),
Title: ko.observable(null),
MaterialId: ko.observable(null),
Alias: ko.observable(null)
};
this.treeData = {
tree: ko.observable(null),
node: ko.observable(null)
};
this.submit = submit;
this.cancel = cancel;
this.openMaterials = menuOptions.openMaterials;
}
// ...
var data = ko.utils.createUnobservable(viewModel.serverData);
// ...
(function(ko) {
ko.utils = ko.utils || {};
ko.utils.createUnobservable = function(observable) {
var unobservable = {};
(function() {
for (var propertyName in observable) {
var observableProperty = observable[propertyName];
if (ko.isObservable(observableProperty) /* always 'false' after ajax */) {
unobservable[propertyName] = observableProperty();
}
}
})();
return unobservable;
};
})(ko = ko || {});
You could fix this by saving a copy of the ko global variable before you include the loaded ajax, and then restoring it afterwards.
var savedKo = window.ko;
.... // do the ajax thing
window.ko = savedKo;