angular.js - update scope and template after jsonp request - javascript

I created a directive that loads some html code inside a tag:
directive:
var appDirectives = angular.module("skdApp", ["offerServices"]);
appDirectives.directive("widget", function() {
return {
restrict: 'A',
template: '<div ng-controller="OfferCtrl"><p>{{offer.price}}</p></div>';
}
service
Besides, I'm using a service to fetch some json data
angular.module('offerServices', ['ngResource']).
factory('Offer', function ($resource) {
return $resource('url/offer.json', {},
{
get: {method: 'JSONP', params: {callback: 'JSON_CALLBACK'}}
}
);
});
controller
The controller adds the received data to the scope
function OfferCtrl($scope, Offer) {
$scope.offer = Offer.get();
}
Now I'm facing the following problem. By the time the template is being loaded, the json request is not finished yet. This results in showing the template with an empty string for {{offer.price}}.
I would need a way to update the template with the corresponding values, once the offerServices has received a response (i.e. the offerService response is available)
Any ideas?

How about using ngCloak
Description
The ngCloak directive is used to prevent the Angular html template
from being briefly displayed by the browser in its raw (uncompiled)
form while your application is loading. Use this directive to avoid
the undesirable flicker effect caused by the html template display.
The directive can be applied to the element, but typically a
fine-grained application is prefered in order to benefit from
progressive rendering of the browser view.

Related

Angularjs ng-include force reload

I have read several posts on this topic, but am still unable to get data loaded via ng-include to re-load after initially being loaded from a remote data server.
(Example How to reload / refresh model data from the server programmatically? )
I have a GSP page which renders a pull down menu of options, and when one is selected, ng-include calls a remote server and displays the data (in an ng-grid). Thus the ng-include needs the full path to the remote server in the URL. This code works correctly the first time an option is used, but will not re-load the data on subsequent selections. I understand that Angularjs is caching the data, but I need to force it to be re-loaded. Is there an event that I can catch inside "myTabController" to force it to call the ng-include? Is it possible to disable the cache? Since it is not possible to pass arguments from Angularjs into the URL of the ng-include, these parameters are being sent via AJAX calls in another section, so that the data server already has the parameters before the ng-include request is made.
_myTabs.gsp
<div ng-controller= "myTabController" ng-init="init(${selIds})">
<select name="tabSelect" ng-model= "tab" ng-options="t.name for t in tabs"></select>
<form name="tabForm">
<div ng-switch="tab.value">
<div ng-switch-when="optionA"<div ng-include="'https://datasrv:8453/DataApp/dataCtrl/aData'" ><div></div>
<div ng-switch-when="optionB"<div ng-include="'https://datasrv:8453/DataApp/dataCtrl/bData'" ><div></div>
<div ng-switch-when="optionC"<div ng-include="'https://datasrv:8453/DataApp/dataCtrl/cData'" ><div></div>
</div>
</form>
</div>
myTabs.js
angular.module('myApp', ['ui.grid', 'ui.grid.resizeColumns', 'ngAnimate', 'ngRoute']);
app.controller("myTabController", function($scope, $interval, $filter, $window, $http, $rootScope, uiGridConstants){
$scope.tabs = {
{ name: '--- Please Select an Option ---', value: ' '},
{ name: 'A Label', value: 'optionA'},
{ name: 'B Label', value: 'optionB'},
{ name: 'C Label', value: 'optionC'}
];
}]);
Edit:
From the replies to my question it appears that calling $templateCache.removeAll() could solve this problem. I can add an onload="xx()" to the ng-include, and a function to myTabController:
$scope.xx = function(){
$templateCache.removeAll();
}
but of course it tells me that $templateCache is not defined. So I tried this inside of a run block:
$scope.xx = function(){
app.run( function( $templateCache ){
$templateCache.removeAll();
});
}
This did not generate an error, but it didn't do anything. The function xx() was being called, but it was not calling $templateCache.removeAll(). What am I missing?
Edit 2.
I tried using a directive to generate the ng-include tag:
<div ng-switch-when="optionA"<div mydir ng-attr-path="https://datasrv:8453/DataApp/dataCtrl/aData" ><div></div>
In the directive, I tried adding a random number to the URL to make it unique:
.directive('mydir', ['$compile', function($compile){
restrict: 'A',
template: function( elem, attr ){
var r = Math.random()
var rpath = attr.path + '/?r=' + r
return "<div ng-include=\"'" + rpath + "'\" onload=\""xx('" + rpath + "')\"></div>";
}
};
Unfortunately this did not make any difference. It did generate the ng-include correctly, including the random string at the end. It displayed the data once, but the URL was never re-generated on subsequent calls. The xx() function was called every time it was loaded, however.
The key is to somehow force this directive to be called each time the menu item is selected, so the random number is called again to change the URL. Or use the xx() function to clear the cache. But how?
Edit 3:
I see that there are problems with passing values using the "{{val}}" notation into a directive into the URL for an ng-include. Reference: modifying ng-include directive
First of all, all angular html when loaded go to $templateCahce, which you can manage manually, like:
$templateCache.put('qwerty', 'pit is best');
then
<div ng-include="'qwerty'" ></div>
You can also remove anything from it using $templateCache.remove.
If you remove template, then angular will try to load new html from server.
Now browser cache may come into play - to disable it you may want add something like ?timestamp= in http interceptor.
Note: your statement about ng-include seems weird to me - of course you can pass arguments to ng-include. And that's what you should do:
<div ng-include="myURL"> </div>
so u first do myURL = 'https://datasrv:8453/DataApp/dataCtrl/cData/selection=1'
then myURL = 'https://datasrv:8453/DataApp/dataCtrl/cData/selection=2'
and then you should not care about cache at all.
Once your template is loaded using ng-include and you want it to reload on button click or any link click then:-
Case 1:- Your template is already loaded in DOM. you only need to update your properties associated to the elements.
Case 2:- Use $scope.$broadcast to call the function which is mapped to the template.
using $broadcast() you would be able to update you model/properties which is already associated to the DOM, and you template will be reloaded.

Converting jQuery Modal Service to AngularJS directive doesn't allow 2 way binding

I've inherited another legacy system that a previous colleague was working on, he was asked to remove server side components from an app and replace them with AngularJS... it's an old JSP system and everything is a bit of a hack. Anyway, there is a jQuery library within the old system that produces Modal Windows, rather than have to add UI-Bootstrap my colleague converted this jQuery service to an AngularJS directive and it kind of works... but with one exception, here is the directive code...
.directive('popupWindow', ['$document', '$compile', function ($document, $compile, MessagesService) {
"use strict";
return {
restrict: 'A',
scope: {},
link: function (scope, element, attrs) {
var newPopup = new Basebox({
contentUrl: '/ng/xtemplate/widgets/CCC/' + scope.getType + '.html',
type: 'regular',
id: 'sf-basebox',
hideBackground: false,
overlay: true,
resolveTest: {getType: scope.getType, message: scope.message, config: scope.config }, // this is appended to the Modal Window as a ng-init and it renders correctly!
closeOnOverlay: false,
closeIcon: true,
checkOldIe: true,
afterContentLoad: function () {
$compile($document.find("#basebox"))(scope);
scope.$apply();
}
});
newPopup.create();
}
});
}
};
}]);
Now this works as it allows us to import HTML templates, it produces the Modal Window and allows us to set various properties, it also allows is to pass data objects (see the resolveTest property) to the controller of the model window. Everything works! Anything within the angularJS templating {{ }} is rendered and we can use angular directives such as ng-init, ng-repeat, etc, etc... however when trying to have two way binding between the view and the controller nothing seems to work, the data gets rendered from the controller to the view but not the other way around. The view never updates... here is the view and the controller
.controller('reportModelCtrl', function ($scope) {
$scope.val = {};
$scope.val.foo = 'blue';
})
Here is the view...
<div ng-controller="reportModelCtrl">
<input type="text" ng-model="val.foo"> {{ val.foo }} this is by default 'blue' but never updates
</div>
Like I say, I dynamically add an ng-init to the modal template and this works, but when I amend the text box nothing happens. Can anyone think of something I'm missing? Even if I try to update values using methods in the controller and ng-click in the view nothing is updated. Calling $scope.$apply() just results in an error. Do I need to add a form to my view? Sorry the jQuery Basebox library isn't included here but I just thought it would add to the confusion. I just really want suggestions or hints to getting the two way binding to work rather than a full answer.

Where to store remote data used by directive inside ngView? (AngularJS)

Just for example:
I have directive inside ngView with this structure:
.directive('features', function() {
templateUrl: '.../',
link: function(scope) {
// HTTP req, getting remote data.
// Store the remote data to the scope
}
})
When I change the route and return it back, the directive link option is executed again, the scope is empty. It need to wait some time for data-response from the remote server and then showing the data. I trying to avoid the layout stretching.
I am new to angular, I was read the documentation.
My question is: In this case, where the data is good to be saved? Do I need to make a controller? Or I can just cache it?
Note: This directive repeating an array (remote data) and making "features" HTML layout. This directive will be used in another routes with another behaviors.
I do not need code explanations, I can read docs. I accept terminology.
You could store the data in a service, basically to act as a cache as you mentioned. If you need to have that data available on page reloads (hard refresh, your app is reloaded again) you can store it in a cookie/local storage.
app.service("featuresService",function($http){
var cachedFeatures=null;
return{
getFeatures:function(callback){
if(!cachedFeatures){
$http.get("...").success(function(data){
cachedFeatures=data;
callback(data);
})
}else{
callback(cachedFeatures);
}
}
}
})

Render speed issue when resuing a directive in AngularJS

I have the following simple base directive:
angular.module("base", [])
.directive("base", function() {
return {
restrict: "A",
scope: true,
controller: function($scope) {
this.setHeader = function(header) {
$scope.header = header;
}
this.setBody = function(body) {
$scope.body = body;
}
this.setFooter = function(footer) {
$scope.footer = footer;
}
},
templateUrl: "base.html"
}
});
I am passing data to this directive in the following way:
.directive("custom", function() {
return {
restrict: "E",
require: "^base",
scope: {
ngModel: "="
},
link: function($scope, $element, $attrs, baseCtrl) {
//Do something with the data or not...
baseCtrl.setHeader($scope.ngModel.header);
baseCtrl.setBody($scope.ngModel.body);
baseCtrl.setFooter($scope.ngModel.footer);
}
}
});
When I create a list of my custom directives, I notice the custom directives aren't rendering immediately. I have made a Plunker demonstrating this behavior. (You will see the list cells empty for a split second, then the directives will appear)
My goal behind this design is to reuse the template of the base directive and only pass in the data needed for display. In this simple example, $scope.data is exactly what I need to pass in but it may be the case some rules or manipulation need to happen first. Rather than have the controller which queried the data handle this, I wanted to pass it off into the directive, separating the concerns.
So my questions are:
Is there any way to make the directives render faster and avoid the flickering shown in the Plunker?
Is this a best practice for reusing directives with Angular?
The flickering is being caused by the async http request to the "base.html" file. Since the HTML for the base directive has to be loaded from the server, there will be a fraction of time where no content will be displayed.
In order to render the data, Angular will go though these 3 stages:
Fetch the HTML file/template from the server (no content will be displayed)
Compile the HTML template (the DOM is updated but the scope isn't yet linked)
Link the scope to the DOM/template (expected data is displayed)
Option 1 - Use the template attribute
Just replace the templateUrl: "base.html" for the direct HTML content:
//templateUrl: "base.html"
template: '<div class="base"><div class="header bottom-border"><h2>{{header}}</h2><div><div class="body bottom-border"><p>{{body}}</p></div><div class="footer">{{footer}}</div></div>',
You will notice that there won't be any flickering this time (check this plunker).
Option 2 - Pre-load template files
Angular has a built-in template cache ($templateCache) that it uses to check if any HTML template file/content has already been fetched from the server or not. If you populate that cache while the app is loading then Angular will not need to fetch the template to render the directive, it will read it directly from the cache.
You can use Angular's $templateRequest (if you are using the latest beta version of Angular) or $templateCache (for any version).
The difference is that $templateRequest automatically makes the HTTP GET request and stores the result in the $templateCache. On the other one you will have to do it manually, like this:
loadTemplate = function(tpl) {
$http.get(tpl, { cache : $templateCache })
.then(function(response) {
$templateCache.put(tpl, html);
return html;
});
};
loadTemplate('base.html');
Note however that this approach needs your app to have a "loading phase".
Regarding the best practices for reusing directives, you seem to be on the right path. The example is to simple to give any advices... Nevertheless, check the "Best practice" notes in this Angular "Creating Custom Directives" guide.
Edit
(my personal preferences regarding template vs templateUrl)
If the main goal is solely performance (i.e. page render speed) then template seems to be the best choice. Fetching one file will always be faster than fetching two... However, as the app grows, the need for a good structure is mandatory, and template files are one of the best practices when it comes to that.
Normally I follow this "rules":
If there are only a few lines of HTML in the template, then just use the template
If the HTML template will not be constantly changing over time (i.e. post structure, contact details, etc...), use template, otherwise (i.e. templates containing banners/ads) use templateUrl
If the app has a loading phase, use templateUrl with cache

Delay the page unload until the model bind

I have a look up service which connects with the API service to bind the dropdown lists.
var Lookup = angular.module('Lookup', [])
.run(function ($window, $rootScope, DropDownLookUp) {
debugger;
$rootScope.MaritalStatusList = DropDownLookUp.maritalStatusList();
$rootScope.ProvinceList = DropDownLookUp.provinceList();
$rootScope.GenderList = DropDownLookUp.genderList();
$rootScope.ProvinceOfEmploymentList = DropDownLookUp.provinceOfEmploymentList();
});
I am using $Http.Get method to fetch the data.
var maritalStatusList = function () {
var keyName = "dropdown-maritalstatus-list";
// debugger;
var data = StoreData.retrieveStaticData(keyName);
if (data == null) {
HttpService.Get(config.apiUrl + "HomeAPI/MaritalStatusLookUp", "maritalStatusList", "maritalStatusList").then(function (results) {
StoreData.saveStaticData(JSON.stringify(results), keyName);
data = results;
return data;
});
}
else {
return data;
}
};
This look up module is being called when my default App module loads.
Service is getting fired correctly. But my page is getting loaded before the above calls completed. Hence no data displayed in the dropdown.
How do I can delay the page load, until I have all the necessary data?
Easy method - Use $routeProvider resolve method. - Delays navigation to a page until all promises are fulfilled
Other method - Don't render dom elements or secondary apps:
.
The gist of the answer below is to simply wrap any necessary dom elements in an ng-if, and set the evaluated expression to true within the success callback of your $http request. The example below is a bit overkill, in that it's using 2 apps on the page.
This is probably an ugly no-no solution, but it does work. In essence, I'm creating multiple apps on the page (which requires manual bootstrapping). The second app has a dependency on the first, and is rendered within the fake "success callback" of the first app's fake $http via ng-if.
Notice there is no ng-app reference, because the app is manually bootstrapped using the element's id:
<section ng-if="loaded" id="myapp" ng-controller="MyNameIsController">
My Name is {{FirstName}} {{LastName}}! {{loaded}}
</section>
I'm simulating an $http request with a $timeout in the first app:
HelloWorldApp.run(function($rootScope, $timeout){
$timeout(function(){
$rootScope.loaded = true;
},1500)
})
Here's the plunker I forked to get it going.

Categories

Resources