AngularJS 2-way binding before API call is complete - angularjs

I am using a basic API call to get an array of items to display in a table. My table loads perfectly fine, but I noticed that Angular is throwing a bunch of errors telling me that it can't interpolate expressions, etc.
I realized that this is because for the split second between page load and the API call response, Angular is trying to render the table and is obviously getting errors because the array used to populate the table does not exist yet.
Therefore, what is the standard way to get rid of this problem? Should I simply use ng-If or ng-hide to stop the render until the API call is complete or is there another way to tell angular to "wait" before rendering the DOM.
I was also able to fix this by initializing the array like $scope.dataArray = [], but I feel like this is a hassle when dealing with complex JSON objects that have many arrays and objects that need to be initialized.
Can someone please explain the best way to do this?

Another way this can be handled is using the resolve property on the router or state provider.
With ui-router's $stateProvider you can do this:
.state('mystate', {
controller: function($scope, data) {
$scope.data = data;
},
resolve: {
data: ['$http', function($http) {
return $http.get('/api/get-data');
}]
}
})
And the promise from the $http.get() will be resolved before the controller is instantiated. This works the same way using ngRoute.
$routeProvider (ngRoute) docs: https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
video showing resolve: https://egghead.io/lessons/angularjs-resolve
another blog explaining resolve: http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx

The solution you've already come upon yourself is the correct one. Even if you are feeding Angular an empty scope variable or collection, that's all fine with the framework. What it won't accept is a complete null reference.
To avoid sullying your controllers, think about slurping up some some models into a parent controller, and nesting related objects, i.e. $scope.magic[], which contains $scope.magic.user, etc.
That's the gospel!

Related

Right approach to helper service (ajax) in Angular js (not dependant on scope)

I have an angular SPA with several modules, services, factories and controllers. I have written a helper service and put it in a common folder minifed js file that all my html pages reference. They contain common bits of data obtained by an AJAX call to a database. It has to run first because the rest of the app depends on values from this helper service. The issue is that the service returns out before the promise has been returned successful, so the helper service is always empty. I do not know what the best approach is to write a helper ajax call and have all files within an angular app reference values returned by it (so everything has to wait before the promise comes back and populates the helper service). I cannot put it in the scope at the top of the main controller, because factories and services cannot reference scope variables is that right? At least I dont understand how if so.
Have searched around and found lots of ways to reference a common service from multiple controllers, but little assistance on how to access that information if the data is a result of an ajax call.
Thanks in advance.
The issue is that the service returns out before the promise has been returned successful, so the helper service is always empty.
Try using resolve:
https://github.com/angular-ui/ui-router/wiki#resolve
https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
Example syntax:
let routerConfig = ($stateProvider, $urlRouterProvider) => {
$urlRouterProvider.otherwise("/login");
$stateProvider
.state('login', {
url: "/login",
templateUrl: "templates/login.html",
controller: "LoginCtrl"
})
.state('home', {
url: "/home",
templateUrl: "templates/home.html",
controller: "HomeCtrl",
controllerAs: "home",
resolve: {
entries: function($rootScope, database) {
return database.getEntries($rootScope.user.uid);
}
}
})
}
There was no point displaying HomeCtrl so I ensured entries from database get resolved before loading... I think you can use similar technique in your code.
If you're using a central service that other pieces of the application depend on, you can use two approaches.
Approach one would be to give the top level controller access to the data and then pass it down to all other components.
The other approach would be giving each individual component/controller direct access to the data.
Either way the code would be kind of similar. There's plenty of ways to do it but the simplest solution I use is just taking advantage of object reference:
Factory:
//you can use factory or service doesn't matter.
.factory('dataFactory', function(){
var store = {
data: null
};
//Return the entire store object
function subscribe() {
return store;
};
function fetchData() {
$http.get('someUrl.com')
.then(function(res) {
//Set data to a property on the store.
store.data = res.data;
});
}
return {
subscribe: subscribe,
fetchData: fetchData
};
}
Controller:
.controller('someController', function(dataFactory) {
//Essentially copying over the store object in the factory by reference
this.store = dataFactory.subscribe();
//call the ajax
dataFactory.fetchData();
}
Template:
<div ng-controller="someController as ctrl">
{{ctrl.store.data}}
</div>
Pros
Any controller in the application can get access to the store in the factory
Since they are referencing the same object, you don't need to do any crazy event emitters or anything to notify all the controllers if the data gets updated in the factory.
You are also caching the data in the factory. If any controllers get destroyed and re-created they can just subscribe again. The data doesn't disappear.
Cons
It's bound by object reference, so any changes made to the store object anywhere in the application will change the object everywhere. This can be good or bad depending on what you want to happen in your application.
If you are working on a large application this can get messy, especially if you're not the only one working on it. However if this is what your application requires and you need a more robust option, i suggest checking out state management tools such as redux.
Checkout this codepen I made which implements a very simple version of redux.

What is needed to make angular meteor helpers work?

this is my first question in stackoverflow!
I have troubles with angular-meteor helpers, i can't get them to work.
I defined this helper block inside my controller.
this.helpers({
testers: () => { return Testers.find() }
});
I created the "Testers" collection.
Testers = new Mongo.Collection("testers");
Also made the publication and subscription with:
$reactive(this).attach($scope);
this.subscribe('testers');
Inside the browser's console i already have access to "Testers" collection and it is reactively updating with Mongo database, i checked that already.
For testing purposes i placed inside the "testers" helper function a console.log and it is indeed getting executed but i dont know why it is not creating the "$scope.testers" variable.
I am using ui-router so i assigned the controller with:
.state('user.index', {
url: '/',
templateUrl: 'client/user/cheques/list.html',
controller: 'testCtrl',
})
I placed a regular variable inside the scope and it gets to the view without trouble as always but i can't get the helper variables.
Does my controller in router assignation matter? Im missing something?
using: angular 1.3.7, angular-meteor 1.3.6
It will create this.testers. you should use controllerAs in your router ,and then use vm.testers (or your other controller as name)

AngularJS - Sharing data from a parent route controller to all child directives on the page

I have a page that has multiple components and I've created each component as a directive. When the page is first loaded, that's when I grab all the data that should be available on the page. So all of the data exists on the controller for that route, which we'll just call pageCtrl. And then what I've been doing is binding any required data to each directive through the attributes, which of course ends up creating an isolate scope for each of them.
I know there are a few ways to share data, so given this situation, is there a recommended way of doing it? Or has anyone had better success doing it one particular way? While it's working perfectly fine the way I'm doing it, I've run into a few caveats. If I need just even one bit of information from the pageCtrl, I need to add another attribute to the directive. So it ends up creating more code on the directive element itself.
I was thinking about just creating a service that would store all the data, which the pageCtrl could initialize, instead of setting it on itself. Any feedback would be appreciated.
good question :)
First solution is to create in parent controller object and pass this object (via ng-model) to all directives. This object will be passed by reference (not by value) so controller and all directives will have access to the same object.
```
// in controller
$scope.shared_data = {someItems: []};
// in html
<my-directive ng-model=shared_data></my-directive>
Second solution is to create some simple service to store all of those data.
// in this solution you have to inject additional service to directive controller
(extended idea of point 2) creating service/factory that will be responsible by collecting and returning data. This service could be injected into directive and use the same methods to collect data. To avoid making multiple calls to API (REST) it could have some cache for each sensitive method.
Communication via events.... (probably the worsts solution for your example)
The first two ideas are probably the best, I do not know full specification of your product so final solution picking belongs to You:).
My advice is to try/play with all of those methods to really understand what is going on and how and when to use each of them :)
You can directly call your parent controller from child directive controller by using $parent.
App.controller('aCtrl', ['$scope', function ($scope) {
$scope.refresh=function(){
.......... //Updated Data get from DB
};
...........
}]);
App.directive('bDirective',function(){
restrict: 'EC',
replace: true,
scope: {},
controller: function($scope) {
$scope.$parent.refresh();
}
...
});
HTML:
<div ng-controller="aCtrl">
<div class="bDirective"></div> //directive
</div>

$watchCollection on an array of js objects in an Angular Controller requires an anonymous function?

I have a simple js array being retrieved by an angular factory, injected into a control, watched, then displayed in a table using ng-repeat from within a view state managed by ui-router.
Initially I attempted to subscribe to this array using $watchCollection...
self.$watchCollection( self.Data, function(newData, oldData){
self.total = newData.length;
});
However this was only running when I initially load the app. I found that the only way to subscribe to the running collection was to use an anonymous function returning the array in question.
self.$watchCollection( function() { return self.Data }, function(newData, oldData){
self.totalWatchedWithAnonFunc = newData.length;
})
View this all in http://plnkr.co/edit/L7mycl
From everything I read, all I should have needed to do for a valid subscription was to pass in the array itself and not an anonymous function that returns the array.
The reason I included ui-router is because my actual application is using it heavily and I wanted to mock up my environment as closely as possible to how I've got things set up.
What am I not understanding? Could I be doing something differently?
Thanks.
Are you looking for self.$watchCollection("Data", ...) instead of self.$watchCollection(self.Data, ...)? Try it out! See the docs to see why: the first argument is a string evaluated on the scope, or a function taking a scope that returns something you want to watch.

Why can't I access params within resolve?

I have a show page and I want to make sure the data is available before i display this page. It was my understanding that using the resolve property in a route was the best way to do this. However with ui-router i Don't have access to attributes in $state.params. Is there any way around this or am I supposed to just query for the object in the controller? I made a simple plunkr to demonstrate the problem. http://plnkr.co/edit/cSgExy7QOpjx7jEEV7v0?p=preview
As per the documentation, you should be referencing $stateParams in your resolve, not $state.params. Also, the Plunkr was broken because you were injecting a service (Tournament) that you didn't include in your excerpt. This works fine:
resolve: {
tournament: function($stateParams) {
console.log($stateParams) // Prints { tournament_id: 1 }
}
}
It seems that the param object doesn't populate fast enough.
If you wrap the alert in the resolve function with a timeout of 0 milliseconds it seems to work.
I don't have any experience with angular ui router but I assume it is related to the digest cycle still processing and the timeout forces the alert to wait until the digest cycle is complete.

Resources