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.
Related
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.
I'm creating a product gallery directive in Angular, which will allow the user to scroll through images with left/right arrows.
What is the most appropriate angular approach to feed my directive with the array of image URLs?
You can assume that the parent controller has already made an API call to receive this the array of URLs
E.g.
<div data-ng-controller="MyController as myController">
<my-image-gallery></my-image-gallery>
</div>
Should I just have an attribute of that takes in the JSON array? Perhaps something like:
<my-image-gallery images="myController.ImageList"></my-image-gallery>
Although, I'm not even sure if the above is possible. It would mean the JSON would have to be converted into a string?
There must be a better way
Edit
As per comments, I've tried the above method, but I can't access the "images" field from within my controller.
Here is what I have defined in my directive:
scope: {
imageSource: '='
},
Then in my controller, I assume I should just be able to reference the variable imageSource, shouldn't I?
I think you're using a kinda weird tutorial or something to learn angular. You can use the MyController as MyController syntax, but the goal of that is to avoid using $scope. I personally don't agree with it and don't understand why people would want to do that.
When you attach a value to $scope it becomes available in your view directly (without needing $scope). For example, $scope.images would be passed in to your directive as just images.
To have the directive process that value as a variable instead of a string it must be defined using an = (as opposed to an #) you can read more about this in the angular directive docs
Here is an example of how this would work:
Javascript
angular.module('app',[])
.controller('myCtrl',['$scope',function($scope){
$scope.imageList=['img1','img2','img3','img...'];
}])
.directive('myImageGallery',function(){
return {
restrict: 'E',
scope:{
images:'='
},
controller: ['$scope',function($scope){
console.log($scope.images);
}],
replace: true,
template: '<ul><li ng-repeat="img in images">{{img}}</li></ul>'
}
})
HTML
<body ng-app="app">
<div ng-controller="myCtrl">
<my-image-gallery images="imageList"></my-image-gallery>
</div>
</body>
and here is a plunker of it in action.
I'm working on a web app which implements a wizard-like behavior. it uses an API to get the "wizard" steps. the API works in a way where you send a request with the current step and all previous answers so far > and get the next step (which also includes the step "name").
My problem is with the URL's of my app, since I need/want them to match the current step. BUT I don't know what is the "current" step until the user already routed to the page.
Example:
user clicks on <a ui-sref="wizard({step: 'second'})"> ('second' is the current step)
$stateProvider than invoke templateUrl e.g: http://whatever.com/getStep/second
server gets the "second" param and passes to the API: current step: second & answer to first step 1 (for example) than receiving the next step HTML and name - lets say: "step_three" and some HTML
Angular renders that HTML
problem with the example above: the user is now in http://myapp.com/#/wizard/second and the HTML that is shown is for the "step_three"
What I would like to do is a request to the server with does params & without routing > than according to the response set the state config: url and template and than "route" to that state. so that the user will be in http://myapp.com/#/wizard/XXX and see the HTML for XXX...
Is this possible? any ideas?
Simplistic approach (you could choose template on the fly in route if you would like)
.state('wizard', function() {
url: 'wizard/:step',
templateUrl: 'views/template/wizard.html',
controller: function($scope, $stateParams, stepData) {
$scope.step = $stateParams.step;
$scope.stepData = stepData;
},
resolve: {
stepData: function(api, $stateParams) {
return api.getdata($stateParams.step);
}
}
})
in wizard html:
<div ng-show='step == "first"'>first data content</div>
<div ng-show='step == "two"'>second data content</div>
<div ng-show='step == "three"'>third data content</div>
if you want to avoid using ng-shows and prefer a different template depending on the route, then use the templateProvider instead of templateUrl:
templateProvider: function($stateParams) {
return a valid string path to the template based on the $stateParams.step value
},
Just a note which might not directly answer your question but give you another option of what is possible to do with ui-router and ng bindings.
In your case you can specify ui-sref as such:
ui-sref="wizard({step: 'second'})"
However you can also use variable bindings inside the ui-sref.
ui-sref="wizard({step: step})"
^
this is a variable in your scope ($scope.step = 'second')
You can also use variable to modify the url-name like:
ui-sref="wizard{{step}}({step: 'second'})"
$scope.step = 'Second'; //results in: ui-sref="wizardSecond({step: 'second'})"
$scope.step = 'Foo'; //results in: ui-sref="wizardFoo({step: 'second'})"
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
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.