DOM not rendered when executing run block - angularjs

I want to show a "Loading..." message while loading json data in my AngularJS app. Showing the message and loading the data is done in my main module's run block. The problem is that the browser hasn't rendered the DOM when the run block is executed so the message is not shown. When exactly is the run block executed? How could I achieve what I want: first show the page, then start loading data and display a message?
My code is here: www.esatoivola.net/kirkkovuosi

"When exactly is the run block executed?"
Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be injected into run blocks. This is to prevent further system configuration during application run time. -- Module docs
.
"How could I ... first show the page, then start loading data and display a message?"
You could use ng-show and a $scope or $rootScope property (initially set to true) to control showing the "Loading" message. Your main controller can load the data, and when the data comes back, set some $scope property tied to the view and set the "showLoadingMessage" property to false.
HTML:
<div ng-controller="MyCtrl">
<div ng-show="showLoadingMessage">Loading... </div>
<ul>
<li ng-repeat="item in data">
....
</li>
</ul>
</div>
Controller:
function MyCtrl($scope, $resource) {
$scope.showLoadingMessage = true;
var dataResource = $resource('/somewhere/else');
dataResource.query( function(data) {
$scope.showLoadingMessage = false;
$scope.data = data;
});
}

Related

How to wait till data is assigned to model in Angularjs?

I have a model which I use to iterate and create a tabular structure on my view. I have a button on click of which I have to get the latest value from DB and assign to model. Then I get the innerHTML of model and create a pdf of my current view.
I am facing a issue that before the data is completely assigned to model and view is refreshed the next statement to get the innerHTML of container div executes and I get the older snapshot of my view. How can I hold the execution of my statement until model is done with assignment and view has refreshed?
You should wrap your code in $timeout. This will make sure that your code is executed in the next cycle and you will get the latest snapshot.
$timeout(function(){
<your code to get innerHtml>
})
There is no need to mention any timeout value as this will be called once view has changed
PS: Add $timeout as dependency.
I would use the ng-show from angular and display your content only when the data is loaded in the controller:
HTML:
<div ng-app="myApp" ng-controller="myCtrl" data-ng-init="getValues()">
<div ng-show="dataLoaded">
<input type="number" ng-repeat="(key, value) in randomArray track by $index" ng-model="randomArray[key]">
</div>
</div>
CONTROLLER:
.controller('myCtrl', function($scope) {
$scope.getValues = function() {
$scope.randomArray = ['myKey', 'anotherKey', 'thirdKey', 'key4'];
angular.forEach($scope.randomArray, function(value, key){
$scope.randomArray[key] = 0;
});
// Set dataLoaded to true
$scope.dataLoaded = true;
}
});
CODEPEN: http://codepen.io/giannidk/pen/KrmgNL?editors=1011

Cannot bind response object from POST to my view

I've been trying to solve this for hours, and have tried to find a working solution on stack overflow and other sites, but none worked so far.
The Issue
I am building a travelogue web app that allows users to log and view their journeys (e.g. a road trip). At the moment I am implementing the feature that lets users view a particular journey in a separate view which they have selected from a list of journeys. I pass down the id of the selected journey and retrieve an Object from MongoDB. I implemented this using POST. It works in that the _id of the selected journey is passed in the request, then used to identify the document with Model.findById - then the response yields the data. The data is bound to $scope.selection.
But while $scope.selection contains the data (when logged to console), I cannot seem to bind it to the view (called view_journey). Meaning, whenever I want to access, e.g. selection.name in my view_journey.html, the expression or ng-bind is left empty.
app.js
$scope.viewJourneybyId = function(id) {
var selectOne = { _id : id };
$http.post('http://localhost:8080/view_journey', selectOne).
success(function(data) {
$scope.selection = data;
$scope.$apply();
console.log("POST found the right Journey");
console.log($scope.selection);
}).error(function(data) {
console.error("POST encountered an error");
})
}
server.js
app.post("/view_journey", function(request, response, next) {
Journeys.findById(request.body._id, function(error, selection) {
if (error)
response.send(error)
response.json({ message: 'Journey found!', selection });
});
});
index.html
<tr ng-repeat="journey in journeys">
<td>
<a href="#/view_journey" ng-click="viewJourneybyId(journey._id)">
{{journey.name}}</a>
</td>
<td>...</td>
</tr>
view_journey.html
<div class="panel panel-default">
<div class="panel-heading">
<h2 ng-bind="selection.name"></h2>
<!-- For Debugging -->
ID <span ng-bind="selection._id">
</div>
<div class="panel-body">
<table class=table>
<caption>{{selection.desc}}</caption>
...
</table>
</div>
</div>
Feedback
This is my first question on stack overflow, so please also tell me if I phrased my question in a way that could be misunderstood, and whether or not I should supply more details, e.g. console output. Thank you ;)
After fiddling with your code I can confirm that when triggering the route you are getting a new instance of the controller that has a new, clean scope. This is the expected behavior with AngularJS.
You can verify this by adding a simple log message as the first line of your controller:
console.log($scope.selected);
You will notice that it always logs out "undefined" because the variable has never been set (within viewJourneyById). If you leave that logging in and test the code you will see the logging fire in viewJourneyById but then immediately the "undefined" as it loads the view_journey.html template into ng-view and creates the new instance of mainCtrl. The presence of the "undefined" after the new view loads shows that the controller function is being executed again on the route change.
There are a couple of ways to address this. First you could create a factory or service, inject it into your controller, and have it store the data for you. That is actually one of the reasons they exist, to share data between controllers.
Factory:
travelogueApp.factory('myFactory',function() {
return {
selected: null,
journeys: []
};
});
Controller:
travelogueApp.controller('mainCtrl', ['$scope','$http','$location','myFactory', function ($scope, $http, $location, myFactory) {
// put it into the scope so the template can see it.
$scope.factory = myFactory;
// do other stuff
$scope.viewJourneybyId = function(id) {
var selectOne = { _id : id };
$http.post('http://localhost:8080/view_journey', selectOne)
.success(function(data) {
$scope.factory.selection = data;
console.log("POST found the right Journey");
console.log($scope.factory.selection);
})
.error(function(data) {
console.error("POST encountered an error");
})
}
}]); // end controller
Template:
<div class="panel panel-default">
<div class="panel-heading">
<h2>{{factory.selection.name}}</h2>
</div>
<div class="panel-body">
<table class=table>
<caption>{{factory.selection.desc}}</caption>
...
</table>
</div>
</div>
More or less something like that. Another way to do it would be to construct the link with the journey id as part of the query string and then in the controller check for the presence of the journey id and if you find one, look up the journey. This would be a case of firing the route, loading a new instance of the controller and then loading the data once you're on the view_journey page. You can search for query string parameters in the controller like this:
var journey_id = $location.search().id;
Either way works. The factory/service method allows you to minimize web service calls over time by storing some data. However, then you have to start considering data management so you don't have stale data in your app. The query string way would be your quickest way to solve the problem but means that every route transition is going to be waiting a web service call, even if you are just going back and forth between the same two pages.

Is there any way to delay ng-view?

I have layout where I have:
<li ng-click="GetLoader();">ACCOUNT</li>
<li ng-click="GetLoader();">SETTINGS</li>
On the index page, I have a menu and ng-view where I can change pages on a click
Also included on the index page is a spinner.
<div class="loading" ng-show="ticketloading" ng-init="GetLoader()">
<div>
<img class="spinner" ng-src="~/Images/ajax-loader.gif" />
</div>
</div>
In my script I have -
$scope.GetLoader = function() {
$scope.ticketloading = true;
loader.css("z-index", "1");
}
My problem is that when a user clicks on "Account" it gets loaded, but just for few milliseconds. Then it changes to all blank. I receive data from ng-view. My question is how can I delay showing ng-view to show the loader a little bit longer.
Thanx in advance!
First of all you should avoid using DOM manipulations in controller. In your case it's better to use declarative ngClass directive to set opacity.
Then your actual issue is that you don't want to use static setTimeout to hide loaded, but rather listen $routeChangeSuccess:
$rootScope.$on('$routeChangeSuccess', function() {
$rootScope.ticketloading = false;
});
and use this loading flag in template like you are currently doing.
You can put above event listener in run block for example.
You can add property in your controller, for example dataLoading and add ng-if attribute to ng-view like this:
layout
<div ng-view ng-if="!dataLoading">
controller
function loadData()
{
var self = this;
self.dataLoading = true;
dataService.loadData(params, function(){
...
self.dataLoading = false;
});
}

Angular: update ng-include on CRUD event

I have a constant sidebar in my index.html file that lists projects using ng-include. When a project is created, or updated etc.. I would like the sidebar to automatically update along with it. I'm not sure which part of my code to provide, as hopefully it's a fundamental question that's easy to answer, though the solution eludes me.
Edit: feel I'm almost there, but src doesn't seem to pick up the controller property:
<div class="col col-md-4" data-ng-controller="ProjectsController" data-ng-include src="'{{sidebarUrl}}'"></div>
In my projects controller:
// Update existing Project
$scope.update = function() {
var project = $scope.project ;
project.$update(function() {
$location.path('projects/' + project._id);
$scope.$broadcast('projectUpdated');
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
$scope.sidebarUrl = 'modules/projects/views/list-projects.client.view.html';
$scope.$on("projectUpdated",function(event,args) {
$scope.sidebarUrl=null;
$scope.sidebarUrl= 'modules/projects/views/list-projects.client.view.html';
});
This is where services are your friend. You should start by encapsulating your CRUD operations into a service.
function MyCrudService($http, ...){ ... }
angular.module('my-app')
.service('myCrudService', MyCrudService);
Now, there are several ways you could implement the updating.
Use $rootScope and broadcast a message saying something has changed, and listen for that event in your sidebar controller (assuming you have one).
//Inside your service
function updateProject(proj){
//Update project
$rootScope.$broadcast('project-updated', proj);
}
//Inside your controller
function MySidebarController($scope){
$scope.$on('project-updated', function(){ ... });
}
Encapsulate the eventing logic inside your service to avoid using $rootScope. Just maintain your own list of callbacks and execute them.
//Inside your controller
function MySidebarController(myCrudService){
myCrudService.onProjectChanged(function(){ ... });
}
Expose the shared data on your service that can be databound to.
//Inside your controller
function MySidebarController($scope, myCrudService){
$scope.projects = myCrudService.projects;
}
Personally, I try to avoid $scope in my controllers, but using it for eventing is OK. Still, I might write some kind of directive that would allow me to execute an expression whenever an event fired in order to avoid it.
<my-event-binding event='project-updated' expression='sideBar.updateProjects()' />
Okay, so I had the same requirement(dynamically changing menu items in an included side panel) what I did was to use a controller in the ng-include template. The template would then fetch the relevant menu items from a service and update the controller. The view had an ng-repeat directive to show all the menu items (projects in your case).
<div ng-controller="ProjectsCtrl">
<ul>
<li ng-repeat="project in projects">
<a ng-href="project.url">
{{project.name}}
</a>
</li>
</ul>
</div>
The controller function could look something like:
function($scope, projectsSvc){
$scope.projects = [];
loadProjects();
$scope.$on("updatedProjects", loadProjects);
function loadProjects(){
projectsSvc.getProjects.success(function(projects){
$scope.projects = projects;
});
}
}
Projects are fetched from a service. When you update a project, broadcast an event that triggers a load of the projects again.
So after the new projects have been committed into the service backend, the sidebar will update accordingly.

How do I load an external HTML file and have the scope variables renderred properly in angularJS?

What I am trying to do is after clicking on a buddy in the buddy list, load a chat dialog template HTML file without disturbing other elements in current DOM just like chatting in facebook.
My problem is that after loading the html template file the scope variables such as {{contact.jid}} are not properly rendered, and the controller for the dialog is not even called.
How can I force a rerender or a call on the controller so that those variables are properly renderred? Or should I not use the jQuery.load function to do this? I can't figure out any other way.
Thank you.
Code of the controllers:
// Controller for the chat dialog
ctrl.controller('ChatCtrl',function($scope){
$scope.test = "Test"
});
// Controller for the buddy list
// $scope.toggleDialog is called when a buddy is clicked
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.toggleDialog = function(to){
$.("#chatarea").load("chatdialog.html")
};
});
The controller function of chat dialog is not called after loading chatdialog.html which has an attribute of ng-controller, so the {{test}} variable is not set.
You need to wrap your content that will be compiled inside a directive.
This directive receives the variables and the HTML that will be compiled.
You can wrap this directive in the element that you like:
http://plnkr.co/edit/RYVCrlDmgVXT3LszQ9vn?p=preview
For example, given the following html and variables:
// In the controller
$scope.controllerHtml = '<span>{{variables.myVariable}}</span>';
$scope.controllerVariables = {myVariable: 'hello world'};
<!--In the HTML-->
<div compile-me html-to-bind="controllerHtml" variables="controllerVariables"></div>
You will get the following:
<div>
<span>hello world</span>
</div>
You are loading the external HTML via jQuery and Angular has no way of knowing how to use it. There are two ways to solve this issue:
use ngInclude to load the template from the server, angular will load it from the server and compile it for you to use.
continue to use jQuery load the HTML from the server and use the $compile service to teach angular how to use it
I would highly suggest using method #1 to load your external template.
I suppose the:
$.("#chatarea").load("chatdialog.html")
is the jQuery .load, or something similar. I would get the template via ngInclude, checking if test is setted or not; html:
<div id="chatarea" ng-if="test">
<div ng-include="'chatdialog.html'"/>
</div>
controller:
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.test = '';
var callbackFunction = function(data) {
$scope.test = data.test;
};
$scope.toggleDialog = function(to){
AjaxRequestToGetBuddyInfoAndMessages(to, callbackFunction);
};
});
Obviously test will be a more complex object, so the ngIf test will be different (and you will need to take into account the fact that:
$scope.test = data.test
if they are objects, they will lose the reference and have an unwanted behaviour).

Resources