Angular $compile misbehaving when returning value - angularjs

I'm trying to use Angular $compile to compile my template.
My template looks like this:
var template = '<div>{{obj.someValue}}</div>';
I'm showing my data in another ctrl/view:
$scope.obj.someValue = "hi";
$scope._html = '';
<div>{{html}}</div>
Now at some point I render my template.
$compile(template)($scope);
$scope._html = template;
At this point I'm confused. Because template hasn't changed.
So I tried the following:
$scope._html = $compile(template($scope);
This does work and compile the template correctly, but my entire console is flooded with errors:
TypeError: Illegal invocation
at Sa (angular.js:265)
at r (angular.js:319)
What am I doing wrong exactly?

Related

Using $compile to compile a piece of HTML in AngularJS

In my controller,
I have a piece of code that outputs a string self.selectedProduct using template string expression..
This works as expected.
var productName = `
<div class='vertical-spacer'>|</div><div> ${self.selectedProduct}</div>`;
But when I want to add a directive that code breaks
var template = angular.element("<my-dropdown domainobject="domainobject"></my-dropdown>");
var linkFn = $compile(template)($scope);
productName = $(`
<div class='domain-wks-vertical-spacer'>|</div><div>`);
productName.append(linkFn);
So instead of the ${self.selectedProduct} I want to show the directive.
But I am not able to.
What am I doing wrong?
You are breaking the string using double quotes (") inside another double quote.
Try this:
var template = angular.element('<my-dropdown domainobject="domainobject"></my-dropdown>');

angularjs: controller hijacking another controller's variable

I am facing a very strange issue with variable in one controller being hijacked by another controller. Here are the details:
In my HTML I have two ng-view tags. Each tag leads to a templateURL (an html) that has its own corresponding controller. Ctrl1 and Ctrl2
the two ng-views are at the same level in the html hierarchy - that is, one is NOT the child of another
Controller1 looks like this:
ngEpMod.controller('Ctrl1',[function() {
selfX = this;
abc = 'abc controller1';
console.log(abc); // break point 1
selfX.query = function() {
console.log("abc=");
console.log(abc); // break point 2
console.log("search query=");
console.log(selfX.searchQ);
lySP.searchHomes();
};
}]);
Controller2 looks like this:
ngEpMod.controller('Ctrl2',[function() {
self = this;
abc = 'abc controller2';
}]);
Both controllers are associated in the html using a "controller as" syntax.
The query() method in Ctrl1 is fired when user user clicks a button (ng-click)
Mystery: As I load the html page ($state) that has the two ng-views, I am observing the browser console. I note that abc value at break-point1 is "abc controller1", but when the query() method is fired, it mysteriously changes to "abc controller2". There is no global variable by that name! As I understand, when the page is being laid out, Ctrl1 is created first so at break-point 1 abc has the correct value, then Ctrl2 is created and somehow it high-jacks the abc variable! Stranger even is that I noticed this problem first with my self variable (self = this) and then I introduced abc just for additional check
Gurus, I am a newbie and would really appreciate your help.
By creating a variable without var (or let in ES6), you've created a global variable attached to window:
abc = 'abc controller1'; equals to window.abc = 'abc controller1';
When the 1st controller instantiates it declares the variable abc on window. When the 2nd controller instantiates, it changes the global abc variable content.
To avoid it in this case define var abc in both controllers.
To avoid it in the future add 'use strict'; to each function deceleration, for example:
ngEpMod.controller('Ctrl2',[function() {
'use strict';
self = this;
var abc = 'abc controller2';
}]);
Strict mode will throw error when you make this mistake (any many others). From MDN:
First, strict mode makes it impossible to accidentally create global
variables. In normal JavaScript mistyping a variable in an assignment
creates a new property on the global object and continues to "work"
(although future failure is possible: likely, in modern JavaScript).
Assignments which would accidentally create global variables instead
throw in strict mode:
I would drop the below code into your app above this instantiation (most modern browsers should understand this syntax for debugging). Call window.trackCtrl in every controller constructor and then pop open the console in chrome or firefox and type printCtrls() and you should get a print out of when they were created in order.
window.trackCtrl = (name) => {
var newCtrl = {}
newCtrl.name = name + '_' + performance.now()
window.trackingCtrls = window.trackingCtrls || []
window.trackingCtrls.push(newCtrl)
}
window.printCtrls = () => Array.isArray(window.trackCtrls) ? window.trackingCtrls.forEach(x => console.info(x)) : console.error('trackCtrls not defined')
This will find bugs such as the controllers getting loaded out of order or duplicate copies of your code or libraries getting loaded on the same page. Performance API helps a lot in these situations => https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

Testing events and document content in karma-jasmine unit-testing

I want to test the following functions using jasmine-karma unit testing but I can't find a way to do so.
function One:
$scope.info = function(text){
$(":root").find("#info").remove();
var htmlObject = "<div id='info' info-alert text='"+$sanitize(text)+"'/>";
var comments = $compile( htmlObject )( $scope );
$(document.body).append( comments );
};
function 2:
$scope.$on('settingsChanged',function(){
$scope.getSettings();
});
I have tried reading the documentation on how to test events and play with the tests but nothing seems to work.
This is my last attempt for the first but the last line is not the right way of doing it I suppose:
it('info', function() {
var text="Some information";
var controller=createController();
scope.info(text);
expect($(document.body).val()).toContain(text);
});
I tried for the second one something similar but nothing seems to work
Your text might have been altered by $sanitize(text)
AngularJS official documentation states that:
- $sanitizeProvider
- service in module ngSanitize
Sanitizes an html string by stripping all potentially dangerous tokens.
So please check if document.body contains your text, and that it has not been altered by $sanitize(text)

Underscore, can't call replace of undefined

I've experienced this error before and tried the solutions I've found on SO for it, but I can't get around it in this case by trying the solutions I've found. I have a question_template that I placed in the header of my index file with the js script tags at the bottom of the file. In the initializer to the view, I get the template using jQuery html function, and the console log shows the template is retrieved from the index.html. However, when i try to insert it into underscore _.template, it's triggering the can't call replace of undefined error
var QuestionView = Backbone.View.extend({
el: $(".east"),
initialize: function(){
var template = $('#question_template').html();
console.log(template);
this.template = _.template(template); #error triggered
},
Since I'm able to log the template, I don't see what my problem is? This is part of the underscore code.
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
source += text.slice(index, offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
First question: What would 'text' represent, which, in my case, is undefined? I would have thought that 'text' is the template, but since I can log my template how is it undefined?
I also have all of the js code (including the initialization of the Question view) wrapped in the document ready, which in other SO questions on this issue was the solution
$(function() {
...code ommitted...
var question_view = new QuestionView({ model: game});
});
Second question: is there anything else I can try
Update
Note, I subsequently pass the model data to the template but it never gets that far because the error is triggered
**$(this.el).html(this.template(response));**
I prepare the templates in three steps
1. var template = $('#question_template').html();
console.log(template);
2. this.template = _.template(template);
3. $(this.el).html(this.template(response));
You should pass the model data to the template,
this.template = _.template(template, this.model.toJSON());

How to use Backbone.Marionette.ItemView with Mustache

The following code works fine using Backbone.Marionette.ItemView but not Mustache.
Backbone.Marionette.ItemView - no Mustache
I would like to use the same code but loading the template varaible using Mustache.
Here is my code:
Backbone.Marionette.ItemView - with Mustache
Any idea why my code does not work and why?
Thanks
I'd like to update the answer here a bit as I was just struggling with this, and I was using this answer as a reference.
Here are my findings:
The answer here is a bit out of date with the current version of Mustache (which is understandable as it's pretty old)
Mustache.to_html is now deprecated, but still exists as a simple wrapper around Mustache.render for backwards compat. Check out this link.
Additionally, I found overriding Marionette.Renderer.render, as in the accepted answer above, completely bypasses the Marionette.TemplateCache layer which may not be the desired behavior.
Here's the source for the Marionette.Renderer.render method:
render: function(template, data){
if (!template) {
var error = new Error("Cannot render the template since it's false, null or undefined.");
error.name = "TemplateNotFoundError";
throw error;
}
var templateFunc;
if (typeof template === "function"){
templateFunc = template;
} else {
templateFunc = Marionette.TemplateCache.get(template);
}
return templateFunc(data);
}
Source
As you can see it accesses the Marionette.TemplateCache.get method and the above answer does nothing to maintain that functionality.
Now to get to my solve (note: the above answer is not wrong necessarily; this is just my approach to maintain the Marionette.TemplateCache layer):
As the comments suggest above, override compileTemplate instead:
Marionette.TemplateCache.prototype.compileTemplate = function(rawTemplate) {
// Mustache.parse will not return anything useful (returns an array)
// The render function from Marionette.Renderer.render expects a function
// so instead pass a partial of Mustache.render
// with rawTemplate as the initial parameter.
// Additionally Mustache.compile no longer exists so we must use parse.
Mustache.parse(rawTemplate);
return _.partial(Mustache.render, rawTemplate);
};
Here's a working JSFiddle as proof.
In the fiddle, I've also overridden Marionette.TemplateCache.loadTemplate to demonstrate that it's only called once. The body of the function only adds some debug output and then re-implements most of the original functionality (minus error handling).
Marionette assumes the use of UnderscoreJS templates by default. Simply replacing the template configuration for a view isn't enough. You also need to replace how the rendering process works.
In your simple example, you only need to override the Marionette.Renderer.render function to call Mustache, and then set the template of your views to the string template that you want:
Backbone.Marionette.Renderer.render = function(template, data){
return Mustache.to_html(template, data);
}
var rowTemplate = '{{ username }}{{ fullname }}';
// A Grid Row
var GridRow = Backbone.Marionette.ItemView.extend({
template: rowTemplate,
tagName: "tr"
});
Note that your JSFiddle still won't work even when you put this code in place, because the GridView is still using a jQuery selector/string as the template attribute. You'll need to replace this with the same type of template function to return mustache.
http://jsfiddle.net/derickbailey/d7qDz/

Resources