I'm using angularFire with Angular to update some views but the strange thing is when I switch from view to view the data doesn't load, but when I refresh the page it does. What's going on?
WizardController:
/* initialize data */
var ref = new Firebase('https://dlwj.firebaseio.com/');
/* set data to automatically update on change */
$scope.currentLocation = $route.current.$$route.originalPath;
$scope.propertyRef = $firebase(ref);
$scope.propertyRef.$on('loaded', function(value) {
//value will be undefined when I switch views using hash routes.
//there is no issue when I just refresh on that page.
console.log(value);
$scope.propertyConfiguration = value.products;
var products = [];
for (var key in value.products) {
if (value.products.hasOwnProperty(key)) {
products.push(key);
}
}
$scope.productsArray = products;
});
console.log('Data retrieved');
Routes:
$routeProvider.when('/SharedProperties',
{
templateUrl: 'partials/SharedPropertiesPartial.html',
controller: 'WizardController'
});
$routeProvider.when('/Registration',
{
templateUrl: 'partials/Registration.html',
controller: 'WizardController'
});
$routeProvider.when('/Login',
{
templateUrl: 'partials/Login.html',
controller: 'WizardController'
});
There is no reason to download the data using a wrapper lib like $firebase (which takes care of synchronization and such) and then immediately pull that data out and put it into a different scope object.
Just declare your scope var:
$scope.products = $firebase(ref);
And to use it:
<ul>
<li ng-repeat="product in products | orderByPriority">{{product|json}}</li>
</ul>
If you need to iterate the data in a controller or service:
$scope.products = $firebase(ref);
// some time later, probably in $scope.products.$on('loaded')...
// note that $getIndex() is only useful here to get the keys in
// the order they appear in the database, otherwise, forEach($scope.products, ...)
// is sufficient
angular.forEach($scope.products.$getIndex(), function(key) {
console.log(key, $scope.products[key]);
});
If you want to use Firebase as a static database (which is quite baffling to a lover of all things real-time like myself) and not be notified each time there is a change, you can simply do the following:
angular.controller('MyController', function($timeout, $scope) {
new Firebase('<URL>').once('value', function(snap) {
$timeout(function() {
$scope.products = snap.val();
});
});
});
And then utilize it normally:
<ul>
<li ng-repeat="(key,product) in products">{{key}}: {{product|json}}</li>
</ul>
Related
Trying to migrate an angularjs application to use the new version of angular-ui-router 1.0.14 and stumbled upon a problem when trying to change $stateParams in the resolve of a state.
For example, previously (when using angular-ui-router 0.3.2) modifying $stateParams worked like this:
$stateProvider.state('myState', {
parent: 'baseState',
url: '/calendar?firstAvailableDate',
template: 'calendar.html',
controller: 'CalendarController',
controllerAs: 'calendarCtrl',
resolve: {
availableDates: ['CalendarService', '$stateParams', function(CalendarService, $stateParams) {
return CalendarService.getAvailableDates().then(function(response){
$stateParams.firstAvailableDate = response[0];
return response;
});
}]
}
})
The problem is firstAvailableDate is populated after a resolve and I do not know how to update $transition$.params() during a resolve when usign the new version of angular-ui-router 1.0.14.
I have tried, and managed to update the url parameter with
firing a $state.go('myState', {firstAvailableDate : response[0]}) but this reloads the state, so the screen flickers
modified $transition$.treeChanges().to[$transition$.treeChanges().length-1].paramValues.firstAvailableDate = response[0]; to actually override the parameters. I have done this after looking through the implementation on params() for $transition$.
Although both those options work, they seem to be hacks rather than by the book implementations.
What is the correct approach to use when trying to modify parameters inside a resolve?
Approach with dynamic parameter:
Take a look at this document: params.paramdeclaration#dynamic. Maybe thats what you are looking for: ...a transition still occurs....
When dynamic is true, changes to the parameter value will not cause the state to be entered/exited. The resolves will not be re-fetched, nor will views be reloaded.
Normally, if a parameter value changes, the state which declared that the parameter will be reloaded (entered/exited). When a parameter is dynamic, a transition still occurs, but it does not cause the state to exit/enter.
This can be useful to build UI where the component updates itself when the param values change. A common scenario where this is useful is searching/paging/sorting.
Note that you are not be able to put such logic into your resolve inside your $stateProvider.state. I would do this by using dynamic parameters to prevent the state reload. Unfortunally, the dynamic rules doesn't work when you try to update your state (e.g. by using $stage.go()) inside the resolve part. So I moved that logic into the controller to make it work nice - DEMO PLNKR.
Since userId is a dynamic param the view does not get entered/exited again when it was changed.
Define your dynamic param:
$stateProvider.state('userlist.detail', {
url: '/:userId',
controller: 'userDetail',
controllerAs: '$ctrl',
params: {
userId: {
value: '',
dynamic: true
}
},
template: `
<h3>User {{ $ctrl.user.id }}</h3>
<h2>{{ $ctrl.user.name }} {{ !$ctrl.user.active ? "(Deactivated)" : "" }}</h2>
<table>
<tr><td>Address</td><td>{{ $ctrl.user.address }}</td></tr>
<tr><td>Phone</td><td>{{ $ctrl.user.phone }}</td></tr>
<tr><td>Email</td><td>{{ $ctrl.user.email }}</td></tr>
<tr><td>Company</td><td>{{ $ctrl.user.company }}</td></tr>
<tr><td>Age</td><td>{{ $ctrl.user.age }}</td></tr>
</table>
`
});
Your controller:
app.controller('userDetail', function ($transition$, $state, UserService, users) {
let $ctrl = this;
this.uiOnParamsChanged = (newParams) => {
console.log(newParams);
if (newParams.userId !== '') {
$ctrl.user = users.find(user => user.id == newParams.userId);
}
};
this.$onInit = function () {
console.log($transition$.params());
if ($transition$.params().userId === '') {
UserService.list().then(function (result) {
$state.go('userlist.detail', {userId: result[0].id});
});
}
}
});
Handle new params by using $transition.on* hooks on route change start:
An other approach would be to setup the right state param before you change into your state. But you already said, this is something you don't want. If I would face the same problem: I would try to setup the right state param before changing the view.
app.run(function (
$transitions,
$state,
CalendarService
) {
$transitions.onStart({}, function(transition) {
if (transition.to().name === 'mySate' && transition.params().firstAvailableDate === '') {
// please check this, I don't know if a "abort" is necessary
transition.abort();
return CalendarService.getAvailableDates().then(function(response){
// Since firstAvailableDate is dynamic
// it should be handled as descript in the documents.
return $state.target('mySate', {firstAvailableDate : response[0]});
});
}
});
});
Handle new params by using $transition.on* hooks on route change start via redirectTo
Note: redirectTo is processed as an onStart hook, before LAZY resolves.
This does the same thing as provided above near the headline "Handle new params by using $transition.on* hooks on route change start" since redirectTo is also a onStart hook with automated handling.
$stateProvider.state('myState', {
parent: 'baseState',
url: '/calendar?firstAvailableDate',
template: 'calendar.html',
controller: 'CalendarController',
controllerAs: 'calendarCtrl',
redirectTo: (trans) => {
if (trans.params().firstAvailableDate === '') {
var CalendarService = trans.injector().get('CalendarService');
return CalendarService.getAvailableDates().then(function(response){
return { state: 'myState', params: { firstAvailableDate: response[0] }};
});
}
}
});
I have values that I want to edit in an other page
<tr ng-repeat="facilitator in listFacilitators" ng-click="showDetails(facilitator);goToFacilitatorP()">
<td>{{facilitator.username}}</td>
<td><li><ul>{{facilitator.cwsessionsAnime}}</ul></li></td>
<td><li><ul>{{facilitator.profilFollow}}</ul></li></td>
</tr>
when I click on I'm supposing to be redirected in another page to show the details and edit them , so I tried to do this in the controller:
$scope.showDetails= function(facilitator){
$scope.selectedFac= facilitator;
}
and in the second page, I do this:
<tr>
<td> {{selectedFac.username}}</td>
<td> {{selectedFac.lastname}}</td>
<td> {{selectedFac.firstname}}</td>
<td> {{selectedFac.title}}</td>
</tr>
it works in the same page but not when I'm redirected, can you help me please?
UPDATE:
I do this but I still haven't data in the seconde page:
1- In the first controller for the 1 page I declared:
profilCtrl.controller('ProfilCtrl', [ '$scope','$location', 'profilService', 'facilitatorPService', function($scope,$location, profilService, facilitatorPService) {
/* ---- appel a facilitator Service ---- */
var facilitator = '';// What ever this is set to in the first place
facilitatorPService.facilitator = facilitator;
2- In my 2nd controller (for the second in where I want to show the details) , I have declared:
facilitatorPCtrl.controller('facilitatorPCtrl', [ '$scope','$rootScope','$cookieStore','$location','membreService','facilitatorPService','userService',function($scope,$rootScope,$cookieStore,$location, membreService,facilitatorPService, userService) {
facilitatorPservice.editFacil= function($scope){
$scope.showDetails = function(){
$scope.selectedFac = facilitatorPService.facilitator;
}
};
3- Ans in my service facilitatorPService I have this:
facilitatorPService.factory('facilitatorPService', [ '$resource','$http', function($resource,$http) {
var service = {
getAllFacilitators : function($scope){
return $resource('/gari-web/services/facilitators/AllFacilitators', {}, {
query : {
method : 'GET', isArray:true,
}}
});
},
editFacil: function($scope){
var self= this;
self.facilitator={};
}};
return service;
} ]);
4- in My html page I put this:
<td>{{selectedFac.username}}</td>
Can someone please tell me what I did wrong, I don't find the mistake
Controllers are 'flushed' when you change views. To keep data from a view to another, store your data within a Service.
UPDATE
.service('FacilitatorService', [
function() {
var self = this;
self.facilitator = {};
}
])
Then in your controllers, inject yourself the service you just created.
.controller('FirstController', ['FacilitatorService',
function(FacilitatorService) {
var facilitator = '';// What ever this is set to in the first place
FacilitatorService.facilitator = facilitator;
}
])
And in your second controller
.controller('SecondController', ['FacilitatorService', '$scope',
function(FacilitatorService, $scope) {
$scope.showDetails = function(){
$scope.selectedFac = FacilitatorService.facilitator;
}
}
])
Like this, your FacilitatorService.facilitator data will be accesible in all your controllers that use FacilitatorService
I recommend using ui router, this resolve two problems, the view change and the data share.
Read more about router ui
doc http://angular-ui.github.io/ui-router/site/#/api/ui.router
The current state
$stateProvider
.state('current', {
url: "/curent",
templateUrl: 'current.html',
controller: 'currentCtrl'
})
Then you can choose how change the view and share the data, the first is the controller, the second is html. Only use one
From current controller (option 1)
$scope.showDetails= function(facilitator){
$state.go('togo', {myCurrentdata: facilitator}) ;
}
From current html (option2)
<tr ng-repeat="facilitator in listFacilitators" ui-sref="togo({myCurrentdata : facilitator})">
<td>{{facilitator.username}}</td>
<td><li><ul>{{facilitator.cwsessionsAnime}}</ul></li></td>
<td><li><ul>{{facilitator.profilFollow}}</ul></li></td>
</tr>
The state that you want to go
$stateProvider
.state('togo', {
url: "/togo",
templateUrl: 'togo.html',
controller: 'togoCtrl'
param: {myCurrentdata: null}
})
To go controller
if($stateParams.myCurrentdata){
$scope.selectedFac = $stateParams.myCurrentdata
}
Well, I have this project, and ui-router is giving me hard times. I made a quick Plunker demo: http://plnkr.co/edit/imEErAtOdEfaMMjMXQfD?p=preview
So basically I have a main view, index.html, into which other top-level views get injected, like this operations.index.html. The pain in my brain starts when there are multiple named views in a top-level view, operations.detail.html and operations.list.html are injected into operations.index.html, which is in turn injected into index.html.
Basically, what I'm trying to achieve is the following behaviour:
When a user clicks Operations item in the navbar, a page with empty (new) operation is shown. The URL is /operations.
When they select an item in a list, the fields are updated with some data (the data is requested via a service, but for simplicity let's assume it's right there in the controller). The URL is /operations/:id.
If they decide that they want to create a new item, while editing a current one, they click New operation button on top of the list, the URL changes from /operations/:id to /operations.
No matter new or old item, the item Operations in the navbar stays active.
If the user is editing an item, it should be highlighted as active in the list, if they create a new item — New operation button should be highlighted, accordingly.
Now, check out the weird behaviour: go to Operations and then click Operations navbar item again. Everything disappears. The same happens if I do Operations -> select Operation 1 -> select New operation.
Besides, please, check out the part where I try to get the id parameter:
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
if (toParams) {
if (toParams.id) {
for (var i = 0; i < vm.operations.length; i++) {
if (vm.operations[i].id == toParams.id) {
vm.operation = vm.operations[i];
break;
}
}
}
}
});
I am no expert, but it seems too long and complex to be true, especially for such a simple task as getting a request parameter. If I try to check on state change $stateParams the object is empty, hence this workaround. If I try to tamper with states in app.js, things change slightly, but there are always bugs like Operations navbar item losing its active state or other weird stuff.
I know that asking such general questions is uncommon in SO, but I really can't grasp the concept of the ui-router, and I can feel that I'm doing things wrong here, and I would really appreciate any help in pointing me in the right direction of how to properly use ui-router for my purposes. Cheers.
There is the updated plunker
I just used technique from this Q & A: Redirect a state to default substate with UI-Router in AngularJS
I added the redirectTo setting (could be on any state)
.state('operations', {
url: '/operations',
templateUrl: 'operations.index.html',
controller: 'operationsController as op',
// here is redirect
redirectTo: 'operations.new',
})
and added this redirector:
app.run(['$rootScope', '$state', function($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(evt, to, params) {
if (to.redirectTo) {
evt.preventDefault();
$state.go(to.redirectTo, params)
}
});
}]);
and also, I removed the redirection currently sitting in the operationsController.js:
angular.module('uiRouterApp')
.controller('operationsController', function($state, $stateParams, $rootScope) {
var vm = this;
//if ($state.current.name === 'operations') $state.go('operations.new');
And that all above is just to keep the new state - without url. Because the solution would become much more easier, if we would just introduce url: '/new':
.state('operations', {
url: '/operations',
..
})
.state('operations.new', {
//url: '',
url: '/new',
Check the plunker here
So, this way we gave life to our routing. Now is time to make the detail working. To make it happen we would need more - there is another updated plunker
Firstly, we will get brand new controller to both child state views:
.state('operations.new', {
url: '',
views: {
'detail': {
templateUrl: 'operations.detail.html',
controller: 'detailCtrl as dc', // here new controller
...
})
.state('operations.detail', {
url: '/:id',
views: {
'detail': {
templateUrl: 'operations.detail.html',
controller: 'detailCtrl as dc', // here new controller
...
It could be same controller for both, because we will keep decision new or existing on the content of the $stateParams.id. This would be its implementation:
.controller('detailCtrl', function($scope, $state, $stateParams) {
var op = $scope.op;
op.operation = {id:op.operations.length + 1};
if ($stateParams.id) {
for (var i = 0; i < op.operations.length; i++) {
if (op.operations[i].id == $stateParams.id) {
op.operation = op.operations[i];
break;
}
}
}
})
We keep the original approach, and set the op.operation just if $stateParams.id is selected. If not, we create new item, with id properly incremented.
Now we just adjust parent controller, to not save existing, just new:
.controller('operationsController', function($state, $stateParams, $rootScope) {
var vm = this;
//if ($state.current.name === 'operations') $state.go('operations.new');
vm.operation = {};
/*$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams) {
if (toParams) {
if (toParams.id) {
for (var i = 0; i < vm.operations.length; i++) {
if (vm.operations[i].id == toParams.id) {
vm.operation = vm.operations[i];
break;
}
}
}
}
});*/
vm.save = function() {
if(vm.operations.indexOf(vm.operation) >= 0){
return;
}
if (vm.operation.name
&& vm.operation.description
&& vm.operation.quantity) {
vm.operations.push(vm.operation);
vm.operation = {id: vm.operations.length + 1};
}
};
Check the complete version here
I want $http.get method to work when a form is submitted.
Here is my code. The object $scope.questions is being set when the method is called but the data doesn't show up in the div. Moreover, when the $http.get method is outside the signIn() function it works just fine.
$scope.signIn = function(data) {
$location.path('/profile');
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$scope.questions = questionData;
console.log($scope.questions);
});
};
<div>
User Profile
<br/>Question Posted
<br/>
<input ng-model="query.title" id="value" type="text" placeholder="Search by Title..." ">
<div>
<ul>
<li ng-repeat="question in questions | filter: query ">
{{question.title}}
</li>
</ul>
</div>
<br/>
</div>
You need to move your $location.path('/profile') inside your http request. Remember that a http request is async call. You should redirect after getting the data not before.
$scope.signIn = function(data) {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$scope.questions = questionData;
console.log($scope.questions);
$location.path('/profile');
});
};
If you're redirecting to another route with a completely separate scope you will lose any scope you're setting in the success handling.
From what I'm reading you're clicking a button to do an action. After that action you're redirecting to another page with a separate controller and trying to persist the data.
Unfortunately, Angular hasn't figured out a great way to do this. The easiest way to persist data through controllers and scope is to create a service that will store it in one controller and grab it in another controller.
For instance:
$scope.signIn = function(data) {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(questionData) {
$location.path('/profile');
storageService.store("question", questiondata)
});
};
Your new factory to persist data through:
angular.module('moduleName').factory('storageService', [
function () {
return {
store: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get: function(key) {
return JSON.parse(localStorage.getItem(key));
},
remove: function(key) {
localStorage.removeItem(key);
}
}
}
]);
Other controller to access data:
$scope.question = storageService.get("question");
// remove localstorage after you've grabbed it in the new controller
storageService.remove("question");
An alternative to doing the somewhat 'hacky' way of using localStorage to persist data through controllers is to use ui-router and have a resolve on the route you're redirecting to.
For instance:
$scope.signIn = function(data) {
$state.go('profile');
};
In your route file:
.state('profile', {
url: '/profile'
controller: profileControllerName,
templateUrl: 'profileHtmlTemplate.html',
resolve: {
'questions': [function() {
var url = "database/fetch_data.php?query=";
var query = "Select * from question where userId=2";
url += query;
$http.get(url).success(function(res) {
return res.data;
});
}]
}
}
In your profile controller:
Inject your 'questions' resolve into your controller and assign `$scope.question = questions;
This will make the HTTP call as soon as you click the route, return the data if successful, then render the page. It will NOT render the page if the resolve does not return success. This will ensure your data will be loaded before you load the page that depends on that data.
I would highly recommend using services to hold your HTTP calls for specific parts of your application. If you have a GET questions, POST question, PUT question. I would create a questionService and make all my HTTP methods there so you don't have to clutter your routes. You would only have to call:
.state('profile', {
url: '/profile'
controller: profileControllerName,
templateUrl: 'profileHtmlTemplate.html',
resolve: {
'questions': [function() {
return questionService.getQuestions(id).then(function(res) {
return res.data;
})
}]
}
}
My web app depends on one specific variable changing throughout the user's visit. It controls what data the user will see at any given time, essentially akin to a TAG.
If the $scope.tagid = 1, is it possible to have another angular model to instantly update its own dataset when tagid is changed to $scop.tagid = 2?
<script >
function PageCtrl($scope) {
$scope.text = '<?=$tagid?>';
}
$scope.showThread = function(tagid) {
$http({method: 'GET', url: 'api/example/thread/id/' + tagid}).
success(function(data, status, headers, config) {
$scope.appDetail = data; //set view model
$scope.view = './Partials/detail.html'; //set to detail view
}).
error(function(data, status, headers, config) {
$scope.appDetail = data || "Request failed";
$scope.status = status;
$scope.view = './Partials/detail.html';
});
}
</script>
<div ng-controller="PageCtrl">
<input ng-model='text' />
<ul>
<li >
<span>{{text}}</span>
</li>
</ul>
</div>
Above is the skeleton of what i'm looking to do.
I realize that if I wanted to, I could call showThread() after each user action and update the data...however, because of the awy I'm looking to set up the site, It makes more sense to only change the tagid, then have everything else update immediately after, rather than picking and choosing each part of the site I want to update. i.e. there may, in addition to showThread(), be updateHeader(), changeSidebar() etc.
Thanks!
I have personally had success using a service; **Assuming that you are using 2 controllers on 1 page, I would create a service like this:
MyApp.app.service("tagDataSvc", function () {
var _tagId = {};
return {
getTagId: function () {
return _tagId;
},
setTagId: function (value) {
_tagId = value;
}
};
});
Next, inject this service into the controllers where this will be used.
In your main controller where you are controlling the TagId (PageCtrl), you would need to set the shared tagId value with a call to the service: tagDataSvc.setTagId($scope.text) You can do this explicitly, or add a $watch on $scope.text, or whatever you prefer.
Finally, in the second controller that you want to automagically update, add a $watch on this service's getTagId() function like so:
$scope.$watch(function () { return tagDataSvc.getTagId(); }, function (newValue, oldValue) {
if (newValue != null) {
$scope.tagId2 = newValue;
//reload whatever needs updating here
}
}, true);