Angular in a injected cshtml file, $compile not working - javascript

TL;TR; is below
I'm dealing with a huge application where everything is made super-generic to easily expend. One of the components is the dialog. Now before your answer is, use ngInclude or angular templates, let me explain how these work and why we would like to stick to them.
The flow of creating this dialog:
From somewhere in javascript a javascript function is called.
That constructs the container for the dialog. Position, widths, heights, gray background, etc.
Once that is present, a loading indicator will show up, while a GET request takes place to the back-end.
At the back-end Action, a view name is provided and a model.
This view (a .cshtml file) gets loaded into a string builder. To give you an idea of what happens, here is a piece of code where the view gets loaded.
var sb = new StringBuilder(1);
using (StringWriter sw = new StringWriter(sb))
{
.....
var helper = new HtmlHelper(viewContext, viewDataContainer);
using (helper.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
helper.RenderPartial(viewName, model);
}
return sb.ToString();
}
Then the string is returned, in javascript it is eventually set as html: diag.html(jsonResponse.data);
Now the view that I load, contains angular elements, like ng-controller and brackets to show something from that controller. Here is the .cshtml:
#model int
<div ng-controller="dialogGridColumnSelectionController as dgc" ng-init="dgc.init(#Model)">
<table>
<thead>
...
<tbody>
<tr ng-repeat="col in dgc.tableColumns">
<td>
<input type="checkbox" name="some" ng-value="dgc.hide"/>
</td>
<td>{{col.headerName}}</td>
<td>{{col.Description}}</td>
</tr>
</tbody>
</table>
</div>
A few statements:
ng-app was tried later, it should not be needed here because the <body> element already has this.
The dialog is within the <body> element.
No errors, no angular warnings.
{{col.headerName}} shows exactly as how it is shown, so angular is not working.
The controller and all the other required javascript files are already loaded on the page from where this dialog is opened. Also tried to add them here, no difference. I also tried to load angular there again, that does give me a warning that I tried to load it more than once. So the scripts are there.
Now I kind of doubted that this would work from the beginning, but I just want to make sure before we start a major rework of the dialogs.
So my question is, is it possible? Can a view that is initiated from .html() (in javascript) have angular? How do I "start" Angular? Or why is it not working with generated html?
TL;TR and EDIT:
After some digging, I eventually did this:
dialog.html(jsonResponse.data);//dialog is a created jquery element, jsonResponse.data contains the `.cshtml` content
if (options.angularCompile && options.angularScope) {//I've set them in options
options.angularCompile(dialog)(options.angularScope);//= $compile(dialog)($scope) or $compile(dialog.contents())($scope)
}
From the html above, it does fire the init function. That function correctly loads data (from other injected factories).
var self = this;
this.tablePageID = 0;
this.tableColumns = [];
$scope.hello = "hii";
this.init = function (tablePageID) {
self.tablePageID = tablePageID;
self.tableColumns = gridTableFactory.getTableColumns(tablePageID);
}
But once completed and the dialog is shown, it still has no angular working.
Worth noting is that the ng-repeat has done it job at the beginning, there are no items. Looks like it compiles and forgets.

Try executing $scope.$apply(); after using the $compile. You may need to inform angular to update its bindings. Also try to avoid using jQuery directly and use angular.element("#....") instead.

Related

change an angularjs nested template at run time

I have a template which is nested inside another template which I want to load when i click on a button.
So the nested template is loaded dynamically. This is what I have done so far.
This is the main body.html (this loads when a url is provided in the browser e.g. http://url#/newtemplate)
<div ui-view> </div>
Other section of the code has been removed for brevity
This is the new_template.html which I expects it to show when I click a button.
When I put a template name directly like below i.e. when I hard code it
<div ui-view="number1"></div>
It loads the template fully.
This is the dynamic model
<button ng-model="template_name" ng-value="number1">Button1</button>
<div ui-view="{{template_name}}"></div>
{{template_name}}
The above does not load the template as I expected. but it shows the string number1 when
the button is clicked
What can I do for it to load the template....
This is my controller
.state('parent',{
url: '/newtemplate',
views:{
'':{
templateUrl: "parent.tpl",
contoller:"controller",
},
'number1#parent':{
templateUrl:"number1.tpl",
contoller:"formcontroller"
},
'number2#parent':{
templateUrl:"number2.tpl",
contoller:"formcontroller"
},
'number3#parent':{
templateUrl:"number3.tpl",
contoller:"formcontroller"
}
}
})
Strange enough when I used the dot notation it did not work so I have to use the absolute naming method.
I also noticed that when I added the nested views as shown above the time it takes before the template gets loaded take a very long time.
Please I would appreciate any help which can allow me to load a nested view at runtime (possibly very fast)
Expecting more answer
I still hope that the I can make use of ui-view/ui-router because of the ability to make use of controller.
I'm not sure you can use uiView to load html dynamically.
I would try another possible solutions:
Use directives
Using ngInclude
I'll leave you an example with ngInclude: https://next.plnkr.co/edit/M5hl71mXdAGth2TE?open=lib%2Fscript.js&deferRun=1&preview

onsenui cannot change content

I’m using onsen ui version 1, followed https://onsen.io/v1/guide.html to make changes to DOM
This section to be exact
// Add another Onsen UI element
var content = document.getElementById("my-content");
content.innerHTML="<ons-button>Another Button</ons-button>";
ons.compile(content);
The problem is nothing changed on the page.
If i dump “content” variable or dump the HTML element it shows the newly edited version on browser console. but on page still the old one.
ons object is instantiated, compile method is callable, tried different HTML elements.
Either you are doing something incorrectly or it's an angular refresh issue.
For the first scenario it's easier if you provide a codepen, so that we can see the problem. Currently the code you are mentioning is working fine for me here.
For the second scenario actually the third line ons.compile(content); should remove this problem imo, it might be a version issue, or there is some context which I am missing.
If you're doing something angular related then you should also show where you are calling these 3 lines from. In order to work it should be called from something like an ng- event (for example ng-click).
JS:
app.controller('yourControllerName', function($scope) {
$scope.addButton = function () {
var content = document.getElementById("my-content");
content.innerHTML="<ons-button>Another Button</ons-button>";
ons.compile(content);
}
});
HTML:
<ons-button ng-click="addButton()">Add another button</ons-button>
Finally if you are unable to make it work you can do something like
app.controller('yourControllerName', function($scope) {
$scope.addButton = function () {
var content = document.getElementById("my-content");
content.innerHTML="<ons-button>Another Button</ons-button>";
ons.compile(content);
$scope.apply(); // should fix the issue, but not recommended
}
});
PS: You can also try out onsen 2 - there you can:
experiment with the interactive tutorial
try out the vanilla version (without any external framework) - which will not have issues like this one

Angular issue inheriting scripts?

This may seem like an obscure question, but I am having issues with my angular inheriting scripts via an invoke script.
The generalization for this would be as follows:
We have a custom browser that will create a button when a custom "property" is added to an input object on our webpage(where the angular resides). When that property is added (similar to class and ID), it will put a button next to the Input object.
The issue I am having is that it will only work on objects outside of the ng-view.
The code works fine if the input object is located in my main index html file (before ng-view is called).
Is there a way to make it so that the custom scripts can be inherited in, what I believe, is the controllers?
Any tips are greatly appreciated.
EDIT: Here are some code snips that are currently being used to make this functionality possible.
The Browser Side:
string invokeScript = "(function(){function i(n,t){t.parentNode.insertBefore(n,t.nextSibling)}function r(n,t){var o={scannableInputId:n.id,scannerType:t},u=document.createElement(\"span\"),f=document.createAttribute(\"class\");f.value=\"input-group-btn\";u.setAttributeNode(f);var r=document.createElement(\"button\"),s=document.createTextNode(\"scan\"),e=document.createAttribute(\"class\");e.value=\"btn btn-primary\";r.appendChild(s);r.setAttributeNode(e);r.addEventListener(\"click\",function(){window.external.notify(JSON.stringify(o))});u.appendChild(r);i(u,n)}var n=document.querySelectorAll(\"[data-barcode-scan]\");for(var t in n)n.hasOwnProperty(t)&&r(n[t],n[t].getAttribute(\"data-barcode-scan\").toLowerCase())})();";
if (Web != null)
{
await Web.InvokeScriptAsync("eval",
new[]
{
invokeScript
});
}
The Website Side:
<h2>
Item (Demo): <input id="scannedValueDemoTextBox"
name="scannedValueDemoTextBox"
ng-model="scannedValueDemo"
type="text"
data-barcode-scan="single"
style="width: 200px"/>
The "data-barcode-scan="single" is what should trigger the button to appear, which works outside the ng-view/controllers.

AngularJS - inject trusted code from loaded content

I've hooked up a lazy loader in Angular. It pulls in full templates and extracts key information from that full template in order to populate a partial. This full page template has script tags which load in and then register with the existing app. All of this works fine. My problem is that I'd like to remove the only use of jQuery in this approach.
The root issue is that the JS inside of something.js doesn't execute when using $element.html(), but it does execute when using $.html(), despite the script tag being placed in the DOM in both approaches.
Working code, including lazy loader and post-bootstrap registration of lazy-loaded JS:
$http.get("/path/to/file.html").success(function(response) {
// response is a full HTML page including <doctype>
var partial = getOnlyWhatWeNeed(response);
// partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'
// i'd like the following to not rely on full jQuery.
$("#stage").html(partial);
$("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});
I've tried what seems like the logical translation:
$element.html($compile(partial)($scope));
and the DOM is created properly, but the JS inside of the loaded <script> tag doesn't actually execute. My research suggested this was an $sce issue, so I tried:
$element.html($compile($sce.trustAsHtml(partial)($scope));
but i get the same result. the DOM is fine, but the JS doesn't actually execute and so I get undefined controller issues.
I've tried playing with $sce.JS and $sce.RESOURCE_URL but the docs didnt elaborate much so I'm not sure I know whether or not what I'm trying is even right.
I've also tried $element[0].innerHTML but I get the same result as $element.html().
Preemptive disclaimer: I can trust the incoming HTML/JS. I know it's inadvisable. This isn't my baby and it is much more complicated than I explained so please try to stay on topic so other people in this position may not have as hard of a time as I am :)
The $http.get happens in a provider, and the $element.html happens in a directive. I consolidated them to remove noise from the problem.
Jquery will find any script tags and evaluate them (either a direct eval or appending them to the head for linked scripts) when calling html(), see this answer. I'm assuming angular's jquery lite doesn't do this. You would need to effectively replicate what jquery is doing and look for script tags in the html you are appending.
Something like this (although I haven't tested it):
$http.get("/path/to/file.html").success(function(response) {
// response is a full HTML page including <doctype>
var partial = getOnlyWhatWeNeed(response);
// partial is now something like: '<script type="text/javascript" src="/path/to/something.js"></script><div ng-controller="somethingCtrl">{{something}}</div>'
var d = document.createElement('div');
d.innerHTML = partial;
var scripts = d.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
document.head.appendChild(scripts[0]);
}
$("#stage").html($compile(partial)($scope)); // it is necessary to do it once before compile so that the <script> tags get dropped in and executed prior to compilation.
});
This is far from an ideal solution as it gives you no guarantee of when things are loaded and doesn't really handle dependencies across scripts. If you can control the templates it would be simpler to remove the scripts from them and load them independently.

AngularJS event which is triggered after all scopes are applied

I have a AngularJS application where I am loading data from a REST service.
Now what sometimes happens is that the brackets {{}} used to access values from scope are rendered and after that replaced by the real values. Now what I d like to do is add a ng-switch to the top DIV of the application and check whether a global var (e.g. pageLoaded (true|false)) is true or false. If its true, I d like to load the normal page, if its false, I d like to print something like "loading...". So my problem is how can I get notified (e.g. through a Angular Event) if all the data is ready, and is added to scope? Because after that I dlike to set pageLoaded to true.
Is there a way to do this in a generic way? I don't like to implement this per page.
Thanks a lot in advance.
Greets
Marc
You should use ng-cloak for that - http://docs.angularjs.org/api/ng.directive:ngCloak
For showing a loading panel, you can do something like:
<div ng-hide="true">Loading...</div>
So when angular finishes loading, ng-hide will occur and hide the loading panel.
Use ng-cloak to get rid of this sort of problems. And make sure you apply the ng-cloak directive to the body of the page so that this doesn't show up till the data is loaded properly. And use the following styling in your CSS file.
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none;
}
Note: you can even create some other element or div, thats something like a popup or notification bar, which shows "please wait till the data is comnpletely loaded". Set this div to display:none initially and in the Ajax call, change the display property to block/inline as needed and finally make it dispay:none after the success call back.. :)
One of the solutions is you can use ng-bind instead of using {{}} which will show ugly {{}} when the value is not rendered.
<div ng-bind="value">Loading ...</div>
For anyone who is having a problem more to do with the actual question than OP's specific scenario:
I had a fragment that was getting loaded-in after/by the main partial that came in via routing.
I needed to run a function after that subpartial loaded and I didn't want to write a new directive and figured out you could use a cheeky ngIf
Controller of parent partial:
$scope.subIsLoaded = function() { /*do stuff*/; return true; };
HTML of subpartial
<element ng-if="subIsLoaded()"><!-- more html --></element>

Categories

Resources