Running angular app inside shadow dom - angularjs

I am developing an application, where I've to load other angular(1.x) application inside current page (As per project requirement). I am trying to achieve it using shadow DOM concept (This could be done using iframe, but I am looking for better approach). I've tried below code:
var templateUrl = "angular-app.html",
templateReceivedCallback = function (response) {
var templateHolder = $("#template-holder"),
div = document.getElementById('template-holder'),
shadowRoot = div.attachShadow({
mode: 'open'
});
shadowRoot.innerHTML = response;
};
$.get(templateUrl, templateReceivedCallback);
I am expecting that the angular application should be loaded inside template holder element:
<div id="template-holder"></div>
The other app is loading in the div which I mentioned, but expressions are not compiled as expected. Example - Angular app having variable this.greeting = "Welcome!" and I am expecting this:
Welcome!
But it rendered as:
{{greeting}}
Is there any better way to achieve this? If yes, please share running example with me.
Thanks.

I think it's not possible because Angular parsing engine won't look inside the Shadow DOM.
As a workaround, you could put the {{greeting}} in the normal DOM.
If you want to use Shadow DOM, you will always be able to access it with <slot> tags.

Related

Trouble implementing google sso in AngularJS application

So I have been trying to implement google single sign on into my angular application; however, sometimes when I reload the page the button disappear. My angular application is using angular routing. If I were to put my button outside of this it would work as expected. It just runs into problem when its loaded through a partial. Any idea how I can fix this?
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<div ng-view></div>
As #agektmr said, the problem is related to the way angular and platform.js interact with each other.
In order to use the auto rendered button you need to trigger the library when the DOM is loaded.
What I did is calling the following code in the onComplete method (I'm working with AngularMaterial dialogs, but you should be able to find a similar method quite easily):
$timeout(function() {
$window.gapi.signin2.render('g-signin2');
});
The only difference is in your html you should change your div and instead of adding it a g-signin2 class you should add an g-signin2 id:
<div id='g-signin2' data-onsuccess='yourMethod'></div>
If you're willing to learn more about Google's implementation you could take a look here.
I'd recommend using imperative approach for implementing the button for this.
<div id="signin">
<button>sign-in</button>
</div>
<script>
document.querySelector('#signin').addEventListener('click', function() {
var auth2 = gapi.auth2.getAuthInstance();
auth2.signIn();
});
</script>
Find more concrete example here
https://github.com/GoogleChrome/google-sign-in
The code you indicated didn't work because of timing. platform.js library tries to take care of it but fails because it's before angular renders DOM.

Angularjs - Charts.js: Same chart element doesn't redraw on other view

I am new to angularjs, trying to create my first directive. I am creating a directive to load Charts.js2.0(beta) into my application.
I have 2 views managed by angular-route, both html view has ng-included a html page that contains only charts-element.
The problem is the first page properly draws the chart, when i go to other view the charts div is loaded but charts is not re-drawn. And now if i go back to first view its blank.
Link to Plunker
What i am doing wrong? Is there any issue with my directive?
Thanks in advance.
There appears to be an issue with the Charts library modifying the existing object on the root scope, and thereby ignoring it forever afterward. I can't really trace down what is doing it, but here's a fix for you: http://plnkr.co/edit/jDQFV62FSeXAQJ6o7jE8
Here is what you had
scope.$watch('config', function(newVal) {
if(angular.isDefined(newVal)) {
if(charts) {
charts.destroy();
}
var ctx = element[0].getContext("2d");
charts = new Chart(ctx, scope.config);
//scope.$emit('create', charts);
}
});
Above, you can see that you're passing scope.config directly into the charts method. That appears to be modifying the data somehow, and since that's passed by reference, you're actually modifying $rootScope.sales.charts. If you copy that object and use it locally like below, you don't have that problem.
Here's how I fixed it.
scope.$watch('config', function(newVal) {
var config = angular.copy(scope.config);
if(angular.isDefined(newVal)) {
if(charts) {
charts.destroy();
}
var ctx = element[0].getContext("2d");
charts = new Chart(ctx, config);
//scope.$emit('create', charts);
}
});
You can see that instead of passing that object directly in, we use angular to make a copy (angular.copy()), and that's the object we pass in.
I think it has relation with the id of the canvas where you are drawing. I've had this problem too amd it was because i was using the same id for the canvas of two graphs in different views. Be sure that those ids are different and that the javasrcipt of each graph is in the controller of each view or in each view itself.
Taking a look at your pluker I see that you are using the same html for the graph and I guess that when angular moves from one of your views to the other thinks that the graph is already drawn. Differentiating two graphs will solve the problem. I don't know of there is any other approach that allows using the same html for the canvas of the graph.
Hope it helps you solve it

How to refresh iframe url?

I am creating an app using ionic in which I am displaying a URL using iframe.
This is the HTML code:
<iframe id="myFrame" width="100%" height={{iframeHeight}}>
This is the angular js:
$scope.iframeHeight = window.innerHeight;
document.getElementById("myFrame").src = value['url'];
Everything is working, however when I make changes on the site from the backend and refresh the app the new changes are not appearing in the app.
The thing you are doing in your code is not a valid way of doing it. You are manipulating DOM using native method that will not run digest cycle, you need to run it manually by doing $scope.$apply(). That will solve your problem but generally you should not do DOM manipulation from controller which is considered as BAD practice. Rather I'd suggest you to use angular two way binding feature. Assign url in scope scope variable and add that on iframe tag using ng-src="{{url}}" so that src URL will be updated by angular and as url get updated iframe will load content from src URL in it.
HTML
<iframe id="myFrame" width="100%" ng-src="{{url}}" height={{iframeHeight}}>
Code
$scope.url = "http://example.com"
As you change scope variable in the controller the the src of iframe will also gets change and iframe will reload the content.
Update
To solve caching issue you need to append current date time to you url that will every time make a new URL and it won't be cached by the browser. Something like $scope.url = "http://example.com?dummyVar="+ (new Date()).getTime() by using this it will never harm your current behavior, only you need to append dummyVar with current time value which would be always unique.
$scope.url = "http://example.com?dummyVar="+ (new Date()).getTime()
Refreshing didn't work for me with timestamp as in Pankaj Parkar's answer, so I solved it in a bit of a hacky way, which just toggles ng-if element off and on again, and forces iframe to load.
Template:
<iframe ng-if="!vm.isRefreshing"></iframe>
Controller:
refreshIframe() {
this.isRefreshing = true;
$timeout(() => {
this.isRefreshing = false;
}, 50);
}
As #pankajparkar suggested in his answer, I'd suggest you using angular 2-way data binding.
In addition to that, you need to wrap the urls with $sce.trustAsResourceUrl function in order to your iframe to work.
<iframe id="myFrame" width="100%" ng-src="{{url}}" height={{iframeHeight}}>
And
$scope.url = $sce.trustAsResourceUrl("http://example.com");

Loading angular directives at runtime

I have a service that I call during app.run() and for some reason when I load the files async at that point they don't seem to take.
Here's the service i'm using:
angular.module('nav').service('SubmoduleService', ['submodules_config',
function(config){
this.autoload = function(){
for(var key in config.modules){
for(var i=0; i<config.modules[key].length; i++){
var src = config.modules[key][i].replace(':path', config.path).replace(':name', key);
console.log(src);
var js = document.createElement("script");
js.type = "text/javascript";
js.src = src;
document.body.appendChild(js);
}
}
return true;
};
}]);
Here's the config file:
angular.module('nav').constant('submodules_config', {
path: "scripts/submodules/:name",
modules: {
gallery: [':path/config.js', ':path/directive.js']
}
});
So basically the config defines a module and all the files that need to get loaded for that module.
I see the files get loaded into the DOM, but for some reason when I load the controller that uses that directive, it doesn't work.
NOTE: The directive works when loading the files explicitly.
Any help is appreciated.
E
By default, angular bootstraps the application on the DOM Ready event. When you load scripts asynchronously, this event can be fired before the scripts load, so angular won't know about the directives contained in them when the DOM is $compiled().
There are quite a few ways to work around this, but they all revolve around deferring compilation of the DOM until the required modules are loaded.
On simple (but not the only) way to defer compilation for a fragment of your application is to simply use ng-if. In pseudo code, it would look something like this:
<div ng-if="moduleWithMyDirectiveLoaded" my-directive></div>
This leaves you with the task of figuring out how to determine if a particular script has been loaded and getting that information into your angular application.
Unfortunately, this isn't trivial. You can write this yourself, but others have already done it for you and you'd probably be better off using one of those tested, cross-browser solutions.
require.js comes to mind as an option, but there are many others that would also work.

Preventing an image from loading until it is within the viewport using ng-src

I would like to be able to prevent an image that has an ng-src attribute from loading until it is visible in the viewport.
Is this possible with Angular?
Previously I have used the jQuery LazyLoad Plugin , however I am trying to do this without having to have both Angular and jQuery.
In case you're still interested, i found out this repo on github :
https://github.com/afklm/ng-lazy-image
I tried it and it rocks !
Images are loaded only when appearing in viewport AND you can choose which image to load based on screen size. It means that you can load smaller images for mobile users ;)
In angularjs, lazy loading can be implemented using a directive.
Steps:
Step 1. create directive as follows:
App.directive('lazyLoad', lazyLoad)
function lazyLoad() {
return {
restrict: 'A',
link: function (scope, element, attrs) {
const observer = new IntersectionObserver(loadImg);
const img = angular.element(element)[0];
observer.observe(img)
function loadImg(changes) {
changes.forEach(change => {
if (change.intersectionRatio > 0 && change.target.getAttribute('tempData')) {
change.target.src = change.target.getAttribute('tempData');
}
});
}
};
};
};
Step 2: use lazy-load attribute in tag and replace "ng-src" with tempData
eg. In html
<img ng-repeate="obj in objects" lazy-load tempData="obj.srcURL">
Note: we are not directly assigning src because we want to prevent image from sending request to server to load image.
Important Note: Dependence upon es-6. You may get parse error while making a build.
*Solution: npm install --save gulp-uglify-es
You need to update your gulp file after installing command.
This will install dependency and your code can make a build now.
That's all!!!
Thank you for reading!!!
I think you could just bind the source to a getter that only returns the value if the element is visible (assuming you are using a binding of some sort to trigger visibility of the img).
For example, use ng-src="{{getImgSource()}}" and in your controller:
scope.getImgSource = function(){
if(scope.showImg){
return "myImageUrl.png";
}
return "";
};
However, if that is something that you will have to use a lot, maybe you should look into creating your own directive to do that.

Resources