Well, I'm making a switch using Angular Material switch, but it's not starting with its initial state as you can see on the picture. The label aside it is the value of it.
And as you can see, the switch starts deactivated from the beginning.
To Start, I'm getting the results from a get request, and putting it into an array.
$scope.questions = [];
The value I'm showing on the picture can be false or true.
On the md-switch, I've referenced directly to the property inside of the array.
<tr ng-repeat="quest in questions">
<td><md-switch ng-model="quest.status">{{quest.status}}</md-switch></td>
</tr>
How can I set the value initially for the switch?
This is a Codepen with a test.
http://codepen.io/anon/pen/BjzpNN
but this seems awkward tho. Doing my example it works fine, but with my development environment, it doesn't work.
This is the console.log of my array, I'm getting this as a get request using $resource.
Well, what is the problem?
Edit2:
Even more awkward, using the properties ng-true-value and ng-false-value the switch doesn't get the initial state.
I updated your JavaScript below, to use a service to get the results. However, I am just returning the hard coded data from your sample. You should change the service to go and fetch your data. The service will be used as a promise, and will return when complete.
(function(){
var app = angular.module('test', ['ngMaterial'])
.service('testService', function() {
// This should be modifed to then call your external API to get the data instead of hard coded results
var questions = [
{ name : "teste", status : true},
{ name : "teste2", status : false},
{ name : "teste3", status : true}
];
return questions;
})
.controller('testController', ['$scope', 'testService', function($scope, testService) {
$scope.questions = [];
$scope.questions = testService; // Use service to get data, will update questions when promise returns.
}]);
}())
There is no check on the data loading, so your screen will be empty until the service returns data. However, when the service returns data, your view should be updated.
As your CodePen showed, you do not need the ng-true-value/ng-false-value, just the ng-model to list the field to get the setting from. The ng-true-value is the value to be set when you toggle the switch (Angular md-switch docs if you want something different from True/False (say A/B).
You might also need to use ng-disabled if you want to protect the switches from changing.
Related
I have an angular filter which is formatting its output by the value of some global variable. That one can change asynchronously, and so do the result of the filter. However, the result is not rendered into view. Not event dispatching $apply() on $scope or even $rootScope, would force view to render the new output of this filter.
For me, it seems like that since filters input stays the same for the whole time, angular won't call filters own method (sort of optimisation there, I guess).
Here is a plunker with the minimal demonstration:
https://plnkr.co/edit/aUm0XRzWn1qr8ZFZB2sY?p=preview
There is a workaround by encapsulating filter inside method (on controllers or $scope) which would render in each time view redraws. But it forces me to redeclaring this method in every controller on view that require use of this filter.
Did anyone also encounter this limitation with filters?
This is happening in every angular version I've tested from 1.2.x to 1.5.x
The problem can be solved with a stateful filter.
From the Docs:
Stateful filters
It is strongly discouraged to write filters that are stateful, because the execution of those can't be optimized by Angular, which often leads to performance issues. Many stateful filters can be converted into stateless filters just by exposing the hidden state as a model and turning it into an argument for the filter.
If you however do need to write a stateful filter, you have to mark the filter as $stateful, which means that it will be executed one or more times during the each $digest cycle.
--AngularJS Developer Guide -- Stateful filters
JS
app.filter('myFilter', function() {
function myFilter() {
return someValue
}
//Set filter to $stateful
myFilter.$stateful = true;
//
return myFilter;
})
The DEMO on PLNKR
Or if you don't want to make the filter stateful you can make use of a service
that you inject in both filter and controller. (Which is pretty much standard in angular when you want to share any data between anything and anything)
https://plnkr.co/edit/GUth5licV3ubLUf2maTV?p=preview
.factory('testService', function(){
return {
someValue : 'This is not right',
update : function(str){ this.someValue = str; }
};
})
.filter('myFilter', function( testService ) {
// Your problem is that this function "lives on" the way it was created
return function() {
return testService.someValue;
}
})
.controller('myController', function($scope, $filter, testService) {
testService.update( 'This is OK');
$scope.myMethodCallingMyFilter = function() {
return $filter('myFilter')();
};
})
I have a link that is used to load data, and then I want the CSS styling to change. I want both the data to be loaded and the CSS change to happen from one click event.
<section ng-controller="OffrdHeadCntrl">
<section id="menuOff" ng-class="{true: 'OpenMenu', false: 'ZeroWide'}[MenuStatus]">
<b>Menu</b><br>
<nav>
<ul id='OffrdCat'>
<li>Antiques</li>
<li>Appliances</li>
</ul>
</nav>
</section>
.... More HTML in another section here.
</section>
This is the routeProvider:
var ysshApp = angular.module('ysshApp', [
'ngRoute',
'ysshControllers',
'firebase'
]);
// 'ysshServices'
ysshApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/Antiques', {
templateUrl: 'Client_Pages/Offered_Menu.html',
controller: 'CommonController',
customInput: 'zeg0yv7nxle6a5sr2xl-ezs'
}).
when('/Appliances', {
templateUrl: 'Client_Pages/Offered_Menu.html',
controller: 'CommonController',
customInput: 'zh86tu488b8g6maw9wrfk'
})
.otherwise({ redirectTo:'/Tab_Home_1' });
}]);
When the user clicks 'Antiques', a bunch of data loads. The link triggers the URL to change, and that causes the $routeProvider to use a controller that then loads the data. I also have a function that changes the CSS styling:
This is the controller that loads the data:
var ysshControllers = angular.module('ysshControllers', []);
ysshControllers.controller('CommonController',
function($scope, $http, $route) {
$scope.dbKey = $route.current.customInput;
$scope.urlToDb = 'https://' + $scope.dbKey + '.firebaseio.com/.json';
$http.get($scope.urlToDb).success(function(data) {
var values = [];
for (var name in data) {
values.push(data[name]);
}
$scope.UsedItems = values;
});
// Initially order by date and time
$scope.orderProp = 'Time';
}
);
There is the function/controller? that changes the CSS: (I stripped some stuff out)
function OffrdHeadCntrl($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
I can get either the CSS to change, or the data to load, but not both. I've tried putting the ng-click="OpenCloseMenu()" in all kinds of places, but either the routeProvider will work, or the CSS change will work, but not both.
I've tried adding lines of code to the Common Controller, but that doesn't work. Can I add an event that runs after the data is done loading? I've seen examples that create a service that relates multiple controllers, but it's for binding data, not running a button click event.
I'm trying to change the CSS in order to hide the menu part the page before the data is loaded. I can use the Resolve object property to cause something to be resolved first, before the data is loaded. That would work, but I'm having trouble configuring the controller so it is available. I can get Resolve to work, but I can't get the function to fire. If I add this to the routeProvider, an alert msg will pop up, then once I click OK, the data loads.
resolve: {
// Run code in resolve first
whatever: function() {
alert("it ran");
}
}
This will work for me if I can get the function I need to run, but I haven't figured that out yet.
I successfully got a $routeChangeSuccess to run, but it runs BEFORE the data is loaded.
ysshControllers.controller('OffrdCatChg', function($scope) {
$scope.$on('$routeChangeSuccess', function(){
alert("it ran the success?");
});
});
So, I don't think that is going to help me. I created a new controller, and put the name of the controller into an HTML tag in that page. It runs when the route changes, but it runs before the data is loaded. That would be okay, but some of my other code runs afterwards, and sets the CSS back to the original. There must be a way to test for a particular controller having been run.
Since the data loaded is accompanied by url change, so link may be directly visited, not only from the clicking previous ones, so you should put data-loading in the first place.
As for the CSS changing, it's a state reflection thing, closely related to url changing(If I understand your description right), so you should listen to url change event, in something like a rootController( aside from ng-view), such as:
$scope.$on '$routeChangeSuccess', (e, current, previous) ->
//getting url segments, and updates css as it should be
return
The logic for me is:
click the link ---> router change ---> new controller(data load)
|
|
|--------->routeChangeSuccess event ---> update the css
I was able to find a solution. First of all, I think I had my controller defined wrong. I had a function defined, and it was working, but I don't think it was set up right. I had a function written like this:
function CntrlMyCSS($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
I didn't have it tied to a module, or defined as a controller. This is the new change.
ysshControllers.controller('HideShowInOffered',
ysshControllers.controller('HideShowInOffered',
function CntrlMyCSS($scope) {
$scope.OpenCloseMenu = function() {
if ($scope.MenuStatus === true) {
$scope.OutputStatus = 'MuchWide';}
else
{$scope.OutputStatus = 'FullWideVal';}
};
}
);
So, I guess that even though I added
ng-controller="CntrlMyCSS"
to a HTML tag, and it worked, it wasn't really a controller? idk. I was using the name of a function as the controller. I know that defining it as a controller seemed to make the stuff inside that controller available to my other controller. I still don't really understand, but basically I just added a line at the top of the function to tie it to the module, and define it as a controller, and change the HTML tag to the name of the controller instead of the name of the function.
The other thing that helped was being able to debug the code by setting break points, stepping through lines, and looking at assigned values. If it wasn't for that, I'd probably still be going down roads to nowhere.
The fix was basically just tweaking what I had set up. I tried $routeChangeSuccess, but by stepping through the code, I saw that it was firing before the data loaded. That didn't work for me, because the controller was setting default CSS values for when the page originally loads. So it would change the CSS, and then set it back to original. Which happens so fast, I wouldn't have known that without the debugger.
$scope.$on('$routeChangeSuccess', function(){
alert("it ran the success?");
});
Trying to get a fix through the routeProvider didn't seem to be of any use either. It was basically an issue of the code being available to be called, and learning the timing the program flow through debugging.
The lesson to the story? Take the time to make sure you have a way to step through your code, and determine what the program flow is. There are probably infinite numbers of bad guesses, and maybe only one solution. The odds are against you unless you can debug.
This is the controller that loads the data, then formats the data, then changes the CSS to close the menu.
ysshControllers.controller('CommonController',
function($scope, $http, $route) {
$scope.dbKey = $route.current.customInput;
$scope.urlToDb = 'https://' + $scope.dbKey + '.firebaseio.com/.json';
$http.get($scope.urlToDb).success(function(data) {
var values = [];
for (var name in data) {
values.push(data[name]);
}
$scope.UsedItems = values;
});
// Initially order by date and time
$scope.orderProp = 'Time';
$scope.MenuStatus = false;
$scope.OutputStatus = 'FullWideVal';
}
);
I am writing a small Angular web application and have run into problems when it comes to loading the data. I am using Firebase as datasource and found the AngularFire project which sounded nice. However, I am having trouble controlling the way the data is being displayed.
At first I tried using the regular implicit synchronization by doing:
angularFire(ref, $scope, 'items');
It worked fine and all the data was displayed when I used the model $items in my view. However, when the data is arriving from the Firebase data source it is not formatted in a way that the view supports, so I need to do some additional structural changes to the data before it is displayed. Problem is, I won't know when the data has been fully loaded. I tried assigning a $watch to the $items, but it was called too early.
So, I moved on and tried to use the angularfireCollection instead:
$scope.items = angularFireCollection(new Firebase(url), optionalCallbackOnInitialLoad);
The documentation isn't quite clear what the "optionalCallbackOnInitialLoad" does and when it is called, but trying to access the first item in the $items collection will throw an error ("Uncaught TypeError: Cannot read property '0' of undefined").
I tried adding a button and in the button's click handler I logged the content of the first item in the $items, and it worked:
console.log($scope.items[0]);
There it was! The first object from my Firebase was displayed without any errors ... only problem is that I had to click a button to get there.
So, does anyone know how I can know when all the data has been loaded and then assign it to a $scope variable to be displayed in my view? Or is there another way?
My controller:
app.controller('MyController', ['$scope', 'angularFireCollection',
function MyController($scope, angularFireCollection) {
$scope.start = function()
{
var ref = new Firebase('https://url.firebaseio.com/days');
console.log("start");
console.log("before load?");
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
console.log("start() out");
};
$scope.start();
//wait for changes
$scope.$watch('items', function() {
console.log("items watch");
console.log($scope.items[0]); //undefined
});
$scope.testData = function()
{
console.log($scope.items[0].properties); //not undefined
};
}
]);
My view:
<button ng-click="testData()">Is the data loaded yet?</button>
Thanks in advance!
So, does anyone know how I can know when all the data has been loaded
and then assign it to a $scope variable to be displayed in my view? Or
is there another way?
Remember that all Firebase calls are asynchronous. Many of your problems are occurring because you're trying to access elements that don't exist yet. The reason the button click worked for you is because you clicked the button (and accessed the elements) after they had been successfully loaded.
In the case of the optionalCallbackOnInitialLoad, this is a function that will be executed once the initial load of the angularFireCollection is finished. As the name implies, it's optional, meaning that you don't have to provide a callback function if you don't want to.
You can either use this and specify a function to be executed after it's loaded, or you can use $q promises or another promise library of your liking. I'm partial to kriskowal's Q myself. I'd suggest reading up a bit on asynchronous JavaScript so you get a deeper understanding of some of these issues.
Be wary that this:
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
does correctly specify a callback function, but $scope.items doesn't get assigned until after you've ran the callback. So, it still won't exist.
If you just want to see when $scope.items has been loaded, you could try something like this:
$scope.$watch('items', function (items) {
console.log(items)
});
In my project I needed to know too when the data has been loaded. I used the following approach (implicit bindings):
$scope.auctionsDiscoveryPromise = angularFire(firebaseReference.getInstance() + "/auctionlist", $scope, 'auctionlist', []);
$scope.auctionsDiscoveryPromise.then(function() {
console.log("AuctionsDiscoverController auctionsDiscoveryPromise resolved");
$timeout(function() {
$scope.$broadcast("AUCTION_INIT");
}, 500);
}, function() {
console.error("AuctionsDiscoverController auctionsDiscoveryPromise rejected");
});
When the $scope.auctionsDiscoveryPromise promise has been resolved I'm broadcasting an event AUCTION_INIT which is being listened in my directives. I use a short timeout just in case some services or directives haven't been initialized yet.
I'm using this if it would help anyone:
function getAll(items) {
var deferred = $q.defer();
var dataRef = new Firebase(baseUrl + items);
var returnData = angularFireCollection(dataRef, function(data){
deferred.resolve(data.val());
});
return deferred.promise;
}
I'm just starting to play with angularJS, so maybe I'm asking something easy to do, but I can't find the way to do it.
The situation is the following: I have a list that's populated by an ng-repeat taking the values from a scoped controller variable. This variable is loaded on page load by an jsonp call, and this works fine.
The problem comes when I need to reload this list based on another select. For example, if a select 'day' value in the select I need to show some values and when I select 'week' I need to show others (also loaded via ajax).
What I've tried is to have a service that loads the data and returns it, and in the controller have two methods, one for the first load and another for the second one that does $scope.apply with the variable. I then call this second method on select value change (I've done it with jquery to simplify it until I can fix this).
This is part of my HTML
<div x-ng-controller='LeaderboardCtrl'>
<select id='leaderboard-select'>
<option value='day'>day</option>
<option value='week'>week</option>
<option value='month'>month</option>
</select>
<div x-ng-repeat='p in leaderboard'>
<p>{{p}}</p>
</div>
</div>
And this is part of the code that affects this functionality
var lead = angular.module("lead",[]);
function LeaderboardCtrl($scope,$attrs,$http,jtlanService) {
$scope.leaderboard = [];
$scope.period = 'day';
var data = {
period:$scope.period
};
$scope.loadLeaderboard = function(){
myService.loadLeaderboard(data).then(function(leaderboard) {
$scope.leaderboard = [];
$scope.leaderboard.push.apply($scope.leaderboard,leaderboard);
});
}
$scope.reloadLeaderboard = function() {
myService.loadLeaderboard(data).then(function(leaderboard) {
$scope.$apply(function() {
$scope.leaderboard = [];
$scope.leaderboard.push.apply($scope.leaderboard,leaderboard);
});
});
}
$scope.loadLeaderboard()
}
lead.service("myService",["$http", function($http) {
var myService = {
loadLeaderboard : function(data) {
var promise = $http.jsonp("/widget/leaderboardJSONP?callback=JSON_CALLBACK&_="+new Date(),{
params:data,
cache:false,
ajaxOptions: { cache: false }
}).then(function(response) {
return response.data;
});
return promise;
}
};
return myService;
}]);
$("#leaderboard-select").change(function(){
scope.period = $("#leaderboard-select").val();
scope.reloadLeaderboard();
});
Here's a fiddle with the code: http://jsfiddle.net/WFGqN/3/
Your fiddle is riddled with issues:
There's no ng-app in your mark-up
You need to change the second Framework Extensions dropdown to one of the "No wrap" options
Your service needs to be defined above your controller
Your controller is referencing "jtlanService" but you've defined "myService"
Your $http.jsonp call isn't going to work as is, but you could use can use the echo service (see Ajax Requests on the left side) to emulate requests
You can't and shouldn't be using jQuery events to call Angular controllers. You should use ng-change and not $().change (and even if you were using jQuery for event binding, you should be using $().on('change')).
You didn't need to use $scope.$apply in your loadLeaderboard function, since when you're calling it, you were already inside of of an $apply call.
There's no need for 2 load+reload leaderboard methods.
And after all that, you don't actually need jQuery.
Here's a fiddle that fixes things up and I think gets you what you want: http://jsfiddle.net/WFGqN/5/. You'll of course need to fix the service on your end, but you get the idea.
I recommend reading this SO answer: "Thinking in AngularJS" if I have a jQuery background?
Currently I have an Angular.js page that allows searching and displays results. User clicks on a search result, then clicks back button. I want the search results to be displayed again but I can't work out how to trigger the search to execute. Here's the detail:
My Angular.js page is a search page, with a search field and a search
button. The user can manually type in a query and press a button and
and ajax query is fired and the results are displayed. I update the URL with the search term. That all works fine.
User clicks on a result of the search and is taken to a different page - that works fine too.
User clicks back button, and goes back to my angular search page, and the correct URL is displayed, including the search term. All works fine.
I have bound the search field value to the search term in the URL, so it contains the expected search term. All works fine.
How do I get the search function to execute again without the user having to press the "search button"? If it was jquery then I would execute a function in the documentready function. I can't see the Angular.js equivalent.
On the one hand as #Mark-Rajcok said you can just get away with private inner function:
// at the bottom of your controller
var init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
// and fire it after definition
init();
Also you can take a look at ng-init directive. Implementation will be much like:
// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>
// in controller
$scope.init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
But take care about it as angular documentation implies (since v1.2) to NOT use ng-init for that. However imo it depends on architecture of your app.
I used ng-init when I wanted to pass a value from back-end into angular app:
<div data-ng-controller="myCtrl" data-ng-init="init('%some_backend_value%')"></div>
Try this?
$scope.$on('$viewContentLoaded', function() {
//call it here
});
I could never get $viewContentLoaded to work for me, and ng-init should really only be used in an ng-repeat (according to the documentation), and also calling a function directly in a controller can cause errors if the code relies on an element that hasn't been defined yet.
This is what I do and it works for me:
$scope.$on('$routeChangeSuccess', function () {
// do something
});
Unless you're using ui-router. Then it's:
$scope.$on('$stateChangeSuccess', function () {
// do something
});
angular.element(document).ready(function () {
// your code here
});
Dimitri's/Mark's solution didn't work for me but using the $timeout function seems to work well to ensure your code only runs after the markup is rendered.
# Your controller, including $timeout
var $scope.init = function(){
//your code
}
$timeout($scope.init)
Hope it helps.
You can do this if you want to watch the viewContentLoaded DOM object to change and then do something. using $scope.$on works too but differently especially when you have one page mode on your routing.
$scope.$watch('$viewContentLoaded', function(){
// do something
});
You can use angular's $window object:
$window.onload = function(e) {
//your magic here
}
Another alternative:
var myInit = function () {
//...
};
angular.element(document).ready(myInit);
(via https://stackoverflow.com/a/30258904/148412)
Yet another alternative if you have a controller just specific to that page:
(function(){
//code to run
}());
When using $routeProvider you can resolve on .state and bootstrap your service. This is to say, you are going to load Controller and View, only after resolve your Service:
ui-routes
.state('nn', {
url: "/nn",
templateUrl: "views/home/n.html",
controller: 'nnCtrl',
resolve: {
initialised: function (ourBootstrapService, $q) {
var deferred = $q.defer();
ourBootstrapService.init().then(function(initialised) {
deferred.resolve(initialised);
});
return deferred.promise;
}
}
})
Service
function ourBootstrapService() {
function init(){
// this is what we need
}
}
Found Dmitry Evseev answer quite useful.
Case 1 : Using angularJs alone:
To execute a method on page load, you can use ng-init in the view and declare init method in controller, having said that use of heavier function is not recommended, as per the angular Docs on ng-init:
This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
HTML:
<div ng-controller="searchController()">
<!-- renaming view code here, including the search box and the buttons -->
</div>
Controller:
app.controller('SearchCtrl', function(){
var doSearch = function(keyword){
//Search code here
}
doSearch($routeParams.searchKeyword);
})
Warning : Do not use this controller for another view meant for a different intention as it will cause the search method be executed there too.
Case 2 : Using Ionic:
The above code will work, just make sure the view cache is disabled in the route.js as:
route.js
.state('app', {
url : '/search',
cache : false, //disable caching of the view here
templateUrl : 'templates/search.html' ,
controller : 'SearchCtrl'
})
Hope this helps
I had the same problem and only this solution worked for me (it runs a function after a complete DOM has been loaded). I use this for scroll to anchor after page has been loaded:
angular.element(window.document.body).ready(function () {
// Your function that runs after all DOM is loaded
});
You can save the search results in a common service which can use from anywhere and doesn't clear when navigate to another page, and then you can set the search results with the saved data for the click of back button
function search(searchTerm) {
// retrieve the data here;
RetrievedData = CallService();
CommonFunctionalityService.saveSerachResults(RetrievedData);
}
For your backbutton
function Backbutton() {
RetrievedData = CommonFunctionalityService.retrieveResults();
}
call initial methods inside self initialize function.
(function initController() {
// do your initialize here
})();