AngularJS: Binding JavaScript Received in XHR to the View - angularjs

Working on a reporting application where reports are generated (in HTML) from a BIRT Report Engine object. The report data comes as a JSON string is recieved from XHR. The JSON string contains a combination of HTML and javascript (a function call, specifically). Once received, the report data is stuffed into a for display in the view. The view is put together using AngularJS.
I did some research and found that binding the HTML/javascript to the view in Angular requires the use of $compile. Using that as a basis, i put together some code that will include data and execute code bound from a string defined explicitly in the $scope. But - unless i'm going overlooking something after staring at the same stuff for a couple hours, the approach i'm using does not work with $scope data defined by XHR. Here's a plunkr to show the general idea implemented. Any suggestions would be greatly appreciated.
The HTML
<!DOCTYPE html>
<html data-ng-app="example">
<head lang="en">
<meta charset="UTF-8">
<title></title>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.11.0/ui-bootstrap-tpls.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-sanitize.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="controller" >
<h1>Static Content</h1>
<p><button href="javascript: void(null)" role="link" ng-click="loadSubreport('recv_po_detail.rptdesign', 'static')">PO000007</button></p>
<h1>HTML from static string</h1>
<div compile="snippet"></div>
<h1>HTML from server string</h1>
<div compile="html.body"></div>
<hr />
<button ng-click="alert()">Show XHR Data</button>
</body>
</html>
The Javascript
var app = angular.module('example', []);
app.controller('controller', ['$scope', '$compile', '$http', function ($scope, $compile, $http){
$scope.snippet="<p><button href=\"javascript: void(null)\" ng-click=\"loadSubreport('recv_po_detail.rptdesign', 'compiled')\">PO000007</button></p>";
$http.get('data.json')
.success(function (data) {
$scope.html = data;
});
$scope.loadSubreport = function (filename, source) {
alert("Called from " + source);
};
$scope.alert = function () {{
alert($scope.html.body);
}}
}]);
app.directive('compile', ['$compile', function ($compile) {
"use strict";
return function (scope, element, attrs) {
var ensureCompileRunsOnce = scope.$watch(
function (scope) {
return scope.$eval(attrs.compile);
},
function (value) {
element.html(value);
$compile(element.contents())(scope);
ensureCompileRunsOnce();
}
);
};
}]);

Your watch goes off right at the start, when html.body still is undefined.
Then you run ensureCompileRunsOnce() and unwatch the scope. So the proper report, once loaded, never gets compiled.
I uncommented the line ensureCompileRunsOnce() and get a nice view of the report.
DEMO

Related

Angularjs TypeError: is not a function issue

I have a select that calls a controller in a directive, that in turn calls a function from a service when the user selects a value in the dropdown list. For some reason I'm getting this error:
TypeError: myService.getMessage is not a function
So I created a plunker to pull out the basic functionality and I was able to duplicate the error using a controller to call a basic service, but I'm still not solving it yet.
Here is the code:
HTML Code:
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.2.x" src="https://code.angularjs.org/1.2.22/angular.js" data-semver="1.2.22"></script>
<script src="script.js"></script>
</head>
<body ng-controller="MainCtrl">
<select ng-options="option for option in listOfOptions"
ng-model="selectedItem"
ng-change="selectedItemChanged()">
</select>
<p>This is the selected Item from the model: <b>{{selectedItem}}</b></p>
<p>This is the result of the ng-change function: <b>{{calculatedValue}}</b></p>
</body>
Script Code:
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope, myService) {
$scope.listOfOptions = ['One', 'Two', 'Three'];
$scope.selectedItemChanged = function(){
$scope.calculatedValue = 'You selected number ' + $scope.selectedItem;
//Call the service.
myService.getMessage();
}
});
app.service('myService', function(){
function getMessage(){
alert("You are in myService!");
}
});
I've seen lots of different, much more complicated code for this type of error, but I'm not understanding what is causing this? Any ideas as to the proper way to do this?
What I'm trying to do is to use a function like myService.mySearch(param1) from a controller or directive.
wrong service code, should be:
app.service('myService', function() {
this.getMessage = function() {
alert("You are in myService!");
}
});
.service() is a function which takes a name and a function that defines the service.It acts as a constructor function.We can inject and use that particular service in other components : controllers, directives and filters.
Correct Syntax :
app.service('myService', function(){
this.getMessage = function(){
alert("You are in myService!");
}
});
Main thing is that service is a constructor function. Hence, we can work with this keyword. In background, this code calls Object.create() with the service constructor function, when it gets instantiated.
In addition to Fetra R 's answer,
Factory is mostly preferable in all cases. It can be used when you have constructor function which needs to be instantiated in different controllers. Service is a kind of Singleton Object. The Object return from Service will be same for all controller.
So as it is like a singleton object, you have to declare it as The Fetra R's Answer, or You may write a factory.
app.factory('myService', function() {
return {
getMessage: function() {
alert("You are in myService!");
}
}
});
and use it in the same manner.

AngularJS - two way binding not working using service

I am learning Angular using W3Schools.
I just modified an example about "Services"... The following is the code:
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<p><input type="text" ng-model="num"></p>
<h2>{{num}}</h2>
<h1>{{hex}}</h1>
</div>
<p>A custom service whith a method that converts a given number into a hexadecimal number.</p>
<script>
var app = angular.module('myApp', []);
app.service('hexafy', function() {
this.myFunc = function (x) {
return x.toString(16);
}
});
app.controller('myCtrl', function($scope, hexafy) {
$scope.num = 200;
$scope.hex = hexafy.myFunc($scope.num);
});
</script>
</body>
</html>
When I update the textbox, the "HEX" part is not updating. Why?
Your hexafy.myFunc is called only once when the controller is initialized, hence only the initial value is converted to hex. If you want a function to be called on the value change of a scope variable in runtime, you need filters. AngularJS has a lot of inbuilt filters that are ready to use.
You can also define a custom filter, just like you define services or controllers.
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<p><input type="text" ng-model="num"></p>
<h2>{{num}}</h2>
<h1>{{num | hexafy}}</h1>
</div>
<p>A custom filter that converts a given number into a hexadecimal number.</p>
<script>
var app = angular.module('myApp', []);
app.filter('hexafy', function() {
return function (x) {
return Number(x).toString(16); // Also convert the string to number before calling toString.
}
});
app.controller('myCtrl', function($scope) {
$scope.num = 200;
//$scope.hex = hexafy.myFunc($scope.num);
});
</script>
</body>
</html>
Further reading: AngularJS Filters
NOTE: A filter is a good idea if you're gonna be using the hexafy functionality at multiple places in/across views. Otherwise, it is just an overkill and the $scope.$watch method will work fine for you, as described in other answers
$scope.hex is not updating because there're no way for it update itself.
The hexafy.myFunc is called only once when the controller is loaded for the first time.
If you want want the $scope.hex property to change with num, you might need a watch on the num property.
$scope.$watch('num', function(newVal, oldVal) {
$scope.hex = hexafy.myFunc($scope.num); /// or newVal
}
The function passed in $scope.$watch will be called everytime the value of $scope.num changes.
for more info see https://docs.angularjs.org/api/ng/type/$rootScope.Scope (the watch section)
Hope it helps.
No need of a service here, you can simple use $watch on the num. See below code snippet, it will update your ui, I have updated your controller code, please check.
app.controller('myCtrl', function($scope, hexafy) {
$scope.num = 200;
$scope.hex = "some default val";
$scope.$watch('num', function(newValue, oldValue) {
$scope.hex = newValue.toString();
});
});
Your Text box is only bind to 'num'. '$scope.hex is not binded to your text box'. So that it is not update when you typing text. You could use '$watch' on 'num'. Read here
app.controller('myCtrl', function($scope, hexafy) {
$scope.num = 200;
$scope.$watch('num', function() {
$scope.hex = hexafy.myFunc(parseInt($scope.num));
});
});

block-ui-pattern has no effect

i am trying to implement block-ui into our angular application on an element by element basis. (everything is included, referenced and injected correctly)
We have been trying to implement
block-ui-pattern
with no success.
our $http request is :-
$http.get('/user/123/GetUserAddress/').then(function (data) {
and our block-ui-pattern is :-
< div block-ui block-ui-pattern="/^user\/123\/GetUserAddress\$/">
{{address}}
</div>
This seems to match the documentation, but is failing to work. Am i missing something fundamental?
Our application exposes an isloading flag. initially set to true, and when the $http promise returns, sets this to false.. I realize that it is not in the documentation, however, Is there a way to set
< div block-ui="isloading"></div>
Post by Parash Gami pointed me in the right direction.
I actually ended up writing a custom directive that wraps block-ui
var myBlocker = angular.module('app.Angular.Directive.myBlocker ', []);
myBlocker.directive('myBlocker ', ['$compile', function ($compile) {
return {
restrict: 'A',
scope :{
blockId: '#id',
block: '=',
},
controller: ['$scope', 'blockUI', function ($scope, blockUI) {
var myBlock = blockUI.instances.get($scope.blockId);
$scope.$watch('block', function (newValue, oldValue) {
if ($scope.block === true)
{
myBlock.start()
}
else {
myBlock.stop()
}
});
}],
link: function link(scope, element, attrs) {
element.attr('block-ui', scope.blockId);
element.attr('style', 'min-height:200px;');
element.removeAttr("my-blocker");
element.removeAttr("data-my-blocker");
$compile(element)(scope);
}
};
}]);
This allows me to now simply add the directive to an element
< div id="myId" my-blocker block="loading">
Please check sample code. Just include one CSS and one JS of blockUI and add dependency blockUI, use blockUI.start() method to show loader and use blockUI.stop() method to hide loader. Following example hide loader after 2 seconds. Use it as per your requirement.
<!DOCTYPE html>
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="http://angular-block-ui.nullest.com/angular-block-ui/angular-block-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script type="text/javascript" src="http://angular-block-ui.nullest.com/angular-block-ui/angular-block-ui.js"></script>
</head>
<body ng-app="app.user">
<div ng-controller="tempController">
</div>
</body>
</html>
<script type="text/javascript">
var app = angular.module('app.user',['blockUI']);
app.controller('tempController', function(blockUI,$timeout)
{
blockUI.start();
$timeout(function()
{
blockUI.stop();
},2000)
});
</script>

Loading Google Cloud Endpoint Library from AngularJS-Client doesn't work

I've an working Endpoint, tested successfully with API-Explorer local and on AppEngine. Now I try to develop an AngularJS-Client. I read a lot of stuff the last week, but it doesn't work. My Controller looks like that.
'use strict';
const WEB_CLIENT_ID = "<...>.apps.googleusercontent.com";
const EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
function init() {
window.init();
}
angular.module('stakeholder1', [])
.controller('StakeholderController', ['$scope', '$window',
function StakeholderController($scope, $window) {
$window.init= function() {
$scope.$apply($scope.load_oauth_lib);
$scope.$apply($scope.load_stakeholder_lib)
};
$scope.load_oauth_lib = function() {
gapi.client.load('oauth2', 'v2', null);
gapi.auth.authorize({client_id: WEB_CLIENT_ID,
scope: EMAIL_SCOPE, immediate: false},
null);
};
$scope.is_backend_ready = false;
$scope.load_stakeholder_lib = function() {
gapi.client.load('stakeholderendpoint', 'v1',
function(){
$scope.is_backend_ready = true;
}, 'https://my-stakeholders.appspot.com/_ah/api');
};
$scope.saveStakeholder= function() {
stakeholder = {
"name" : $scope.name
};
gapi.
client.stakeholderendpoint.saveStakeholder(stakeholder).execute();
}
}]);
My mark-up looks like that. If I remove the ng-show-Directive and post a name, I get an error "angular.js:13236 ReferenceError: stakeholder is not defined". So I think the API is not loaded.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html ng-app="stakeholder1">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>MyStakeholders</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="../app/controllers/stakeholder1.js"></script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>
</head>
<body>
<h1>MyStakeholders</h1>
<div ng-controller="StakeholderController" class="container" ng-show="is_backend_ready">
<form ng-submit="saveStakeholder()">
<input type="text" ng-model="name" required><br>
<input type="submit" class="btn" value="Post">
</form>
</div>
</body>
</html>
I'm using Eclipse with GPE. In my WEB-INF-directory there is a generated file stakeholderendpoint-v1.api. Should this file being loaded with gapi.client.load?
I would be glad about some hints or advice how I can debug the load process.
Yes, the stakeholderendpoint should be loaded with gapi.client.load() into the gapi before it is used like gapi.client.stakeholderendpoint.
That file in the WEB-INF directory is important but you can ignore it. The function gapi.client.load() will handle everything behind the scenes.
In your code, the flag $scope.is_backend_ready is indicating if the endpoint is loaded and ready for use. You can protect your UI against errors with this flag. But I recommend to store it in the $rootScope for future use in other controllers. Or even better is to create a service, which loads all the endpoints and store the state of is_backend_ready. Then you can decide to show some progress bar during the loading the endpoints, until the is_backend_ready is true.
Or you can use angular-gapi library.

Template preparation in AngularJs

Is it possible to render template with AngularJs not on Page, but probably in memory? I need to prepare html to be send as email.
I guess i could render something in hidden div, then in some way assign it content to variable , but for me it looks ugly :(
You can take a look at $compile function: http://docs.angularjs.org/api/ng.$compile
Example:
function MyCtrl($scope, $compile){
// You would probably fetch this email template by some service
var template = '</div>Hi {{name}}!</div></div>Here\'s your newsletter ...</div>'; // Email template
$scope.name = 'Alber';
// this produces string '</div>Hi Alber!</div></div>Here\'s your newsletter ...</div>'
var compiledTemplate = $compile(template)($scope);
};
Sure, you can use the $compile service to render a template. The rendered template will be a DOM node that isn't attached to the DOM tree. And you don't have to attach it to get its content. You could do something like this:
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', '$compile', function($scope, $compile){
var compiled;
$scope.compileTemplate = function() {
var template = '<ul><li ng-repeat="i in [1, 2, 3]">{{i}}</li></ul>';
var linker = $compile(template);
compiled = linker($scope);
}
$scope.sendEmail = function() {
alert("Send: " + compiled[0].outerHTML);
}
}]);
</script>
</head>
<body ng-controller="MainCtrl">
<button ng-click="compileTemplate()">Compile template</button>
<button ng-click="sendEmail()">Send email</button>
</body>
</html>
The reason that I've divided them into two different functions here is because when you compile and link it to the scope, the template isn't filled with data until after the next digest. That is, if you access compiled[0].outerHTML at the end of the compileTemplate() function, it won't be filled (unless you use a timeout...).

Resources