AngularJS postmessage to iframe - angularjs

I am looking for a way to obtain an iframe contentWindow object and post a message to it after some action of the user. My current solution does not feel ok with angular at all (especially accessing the DOM from the controller).
I have created a plunker demonstrating the issue:
http://plnkr.co/edit/aXh4jydWGWfK3QQD4edd
Is the a more angular way to execute the postMessage?
controller:
app.controller('Main', function($scope) {
$scope.click = function() {
var iframe = document.getElementById("inner").contentWindow;
iframe.postMessage("Hello iframe", '*');
}
});
html:
<body ng-controller="Main">
<button ng-click="click()">send message</button>
<iframe id="inner" src="inner.html"/>
</body>

I realize your question is over a year old at this point but I've recently had a similar need so I thought I'd post my solution. Originally I had something like you posted but as you pointed out this doesn't feel very "Angular". It's also not easily testable which I supposed is also not very "Angular".
Instead I've refactored my code to implement the iframe as a directive. I then $broadcast() events from my app's controllers and have the directive listen for them. This code can probably be improved quite a bit but it's feels a little more "Angular" and avoids directly touching the DOM.
'use strict';
angular.module('app')
.directive('angularIframe', ['$rootScope', function($rootScope) {
return {
restrict: 'E',
replace: true,
template: '<iframe id="game" src="/iframe/index.html" width="100%" height="100%" frameboarder="0" scrolling="no"></iframe>',
link: function(scope, elem) {
var off = $rootScope.$on('app.postmessage', function(ev, data, targetOrigin) {
var str = JSON.stringify(data);
targetOrigin = targetOrigin || '*';
elem[0].contentWindow.postMessage(str, targetOrigin);
});
// See: http://stackoverflow.com/a/14898795/608884
elem.on('$destroy', function() {
off();
});
}
};
}]);
You can then use this directive by adding <game></game> somewhere in your application.
In a controller you can now broadcast the app.postmessage event along with some data to invoke postMessage() on the iframe:
var myData = { foo: 'bar' };
$rootScope.$broadcast('app.postmessage', myData);

Related

Confused refreshing Angular custom directive / communicating between adjacent directives

I am two weeks into Angular. I have watched several Pluralsite videos and read several post and this has resulted in great progress but also some confusion. I want to notify one directive of some change in another directive. Then refresh the directive. In other words it needs to go back to the server with the selection from the first and pull the appropriate data.
I have read about eventing and things like $watch() but then I have seen others say to avoid watch and to use $emit and $on. I have even seen one article say to use transclusion.
I have access to Pluralsight and other resources. I will self educate if someone could just point my nose in the right direction.
My directive markup html:
<div class="col-md-3">
<dashboard-main-nav></dashboard-main-nav>
</div>
<div class="col-md-3">
<dash-select ng-show="vm.isDashSelectionVisible">Selections</dash-select>
</div>
My app declaration: NOTE I know I need to get the parm from scope but not sure how...
(function ()
{
"use-strict";
...snip controller setup etc..
.directive("dashboardMainNav", function () {
return {
restrict: "E",
templateUrl: "/Navigation/GetDashItems",
scope: true
}
})
.directive("dashSelect", function () {
return {
restrict: "E",
templateUrl: "/Navigation/GetDashSelections/:" + $scope.??,
scope: true
}
});
})();
routingController:
(function () {
...snip...
function routingController($http, $scope) {
var vm = this;
var isDashSelectionVisible = false;
var dashSelectionId = 0;
$scope.LetterSearch= function (dashSelId) {
vm.isDashSelectionVisible = true;
vm.dashSelectionId = dashSelId;
alert("Letters Clicked: " + dashSelId);
}
}
})();
Rendered HTML:
<dashboard-main-nav>
....snip....
Letters
</dashboard-main-nav>
..... snip.....
<dash-select>
Numbers
</dash-select>
I am not showing the $routeProvider config that wires up the routingController as that works fine. I just need to get that custom directive to grab the parm from scope..refresh then update the dom.
Thank You for your patience and knowledge sharing.

Controller method not found when button inside template is pressed

I'm creating a project using NodeJS, Express and AngularJS that will have a search form (added via custom directive) and a search results that must be loaded only after the search button is pressed.
The problem is that the method I have created inside the controller can't be found from the search form.
Here is a sample of my code:
app.js
(function() {
var app = angular.module('app', ['app-directives']);
app.controller('AppController', function() {
this.buttonClick = function() {
alert('Test');
};
});
})();
directives.js
(function(){
var app = angular.module('app-directives', []);
app.directive('searchForm', function() {
return {
retrict: 'E',
templateUrl: '/partials/search-form.html'
};
});
app.directive('searchResults', function() {
return {
retrict: 'E',
templateUrl: '/partials/search-results.html'
};
});
})();
search-form.html
<input type="text" id="query" />
<button onclick="buttonClick">Search</button>
page-content.html
<section id="mainContent">
<search-form></search-form>
<search-results></search-results>
</section>
UPDATE
The second question will be posted in another thread.
About your first question:
You are using onclick attribute instead angular's 'ng-click' in the button search. This could be the problem. And do not forget to also add the 'ng-app' and 'ng-controller' tags. If not, your method will never be visible.
I also would recommend you to use $scope service instead of 'this' for attaching models and functions you later will use in your views.
Regards

Create an ajax loader generic directive

I need some help regarding my very first AngularJS project (after the official tutorial).
It started well, since I was able to control my form, submit my ajax request and display the results as I want with a pagination... Isn't that great? But now I try to make an ajax loader. I think it would have been possible using ng-show directive, but I'd like to make it as generic as possible so I can re-use it on other projects.
So I started to create a directive (following this: Angularjs loading screen on ajax request). Now everything works as I want, except that the ajax loader is never displayed.
It seems that my directive doesn't check the loading parameter. I have the feeling I missed something about the scope.
Here's my code (simplified):
(function() {
var app = angular.module('querygen', []);
app.controller('QuerygenController',['$http', function($http){
this.loading=false;
this.getQueries = function(page){
this.loading = true;
querygen = this;
$http.post('/action.php', data).success(function(data){
querygen.loading = false;
}).error(function(data){
querygen.loading=false;
alert('An error occured.');
});
};
}]);
app.directive('loading', function () {
return {
restrict: 'E',
replace:true,
template: '<div class="loading"><img src="images/loader.gif" alt="Loading..." /></div>',
link: function (scope, element, attr) {
scope.$watch('loading', function (val) {
if (val)
element.show();
else
element.hide();
});
}
};
});
})();
My HTML:
<div ng-controller="QuerygenController as querygenCtrl">
<loading></loading>
<div ng-show="!querygenCtrl.loading">
<div ng-repeat="query in querygenCtrl.queries">
{{query}}
</div>
</div>
</div>
Also, once my issue solved, if I put the directive into another module, and add the new module as a dependency of "querygen", will it work as it is?
I post the answer. MapOfVeins was right. Updating this.loading to $scope.loading solved my issue.
There was definitively something I missed regarding this/$scope.
Thank you!

Angular script won't run when first loaded

On page load the console log prints but the toggleClass/click won't work I even use angular.element but it has the same result.I need to change state in order for the toggleClass to work.I dunno what's wrong in my code.
.run(['$rootScope', function ($rootScope) {
console.log('test');//this prints test and it's ok
//this part won't load at the first loading of page.
$('.toggle-mobile').click(function(){
$('.menu-mobile').toggle();
$(this).toggleClass('toggle-click');
});
//....
}])
even doing it this way doesn't work.
$rootScope.$on('$viewContentLoaded', function () {
angular.element('.toggle-mobile').on('click', function (event) {
angular.element(this).toggleClass('toggle-click');
angular.element('.menu-mobile').toggle();
event.preventDefault();
});
});
The Angular way to render items is different from "On DOM Ready" that is why we need to treat these as 2 separate things.
Angular could render items later on even after DOM is ready, this could happen for example if there is an AJAX call($http.get) and that is why a directive may be the recommended approach.
Try something like this:
<body ng-controller="MainCtrl">
<div toggle-Me="" class="toggle-mobile"> Sample <div class="menu-mobile">Sample 2</div>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {}]);
myApp.directive("toggleMe", function() {
return {
restrict: "A", //A - means attribute
link: function(scope, element, attrs, ngModelCtrl) {
$(element).click(function(){
$('.menu-mobile').toggle();
$(this).toggleClass('toggle-click');
});
}
};
});
...
By declaring the directive myApp.directive("toggleMe",... as an attribute toggle-Me="" every time angular generates the input element it will execute the link function in the directive.
Disclaimer: Since the post lacks from a sample html I made up something to give an idea how to implement the solution but of course the suggested html is not part of the solution.

Angularjs - Hide content until DOM loaded

I am having an issue in Angularjs where there is a flicker in my HTML before my data comes back from the server.
Here is a video demonstrating the issue: http://youtu.be/husTG3dMFOM - notice the #| and the gray area to the right.
I have tried ngCloak with no success (although ngCloak does prevent the brackets from appearing as promised) and am wondering the best way to hide content until the HTML has been populated by Angular.
I got it to work with this code in my controller:
var caseCtrl = function($scope, $http, $routeParams) {
$('#caseWrap').hide(); // hides when triggered using jQuery
var id = $routeParams.caseId;
$http({method: 'GET', url: '/v1/cases/' + id}).
success(function(data, status, headers, config) {
$scope.caseData = data;
$('#caseWrap').show(); // shows using jQuery after server returns data
}).
error(function(data, status, headers, config) {
console.log('getCase Error', arguments);
});
}
...but I have heard time and time again not to manipulate the DOM from a controller. My question is how can I achieve this using a directive? In other words, how can I hide the element that a directive is attached to until all content is loaded from the server?
In your CSS add:
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
and just add a "ng-cloak" attribute to your div like here:
<div id="template1" ng-cloak>{{scoped_var}}<div>
doc: https://docs.angularjs.org/api/ng/directive/ngCloak
On your caseWrap element, put ng-show="contentLoaded" and then where you currently have $('#caseWrap').show(); put $scope.contentLoaded = true;
If the caseWrap element is outside this controller, you can do the same kind of thing using either $rootScope or events.
Add the following to your CSS:
[ng\:cloak],[ng-cloak],.ng-cloak{display:none !important}
The compiling of your angular templates isn't happening fast enough.
UPDATE
You should not do DOM manipulation in your controller. There are two thing you can do...
1. You can intercept changes to the value within the scope of the controller via a directive! In your case, create a directive as an attribute that is assigned the property you want to watch. In your case, it would be caseData. If casedata is falsey, hide it. Otherwise, show it.
A simpler way is just use ngShow='casedata'.
Code
var myApp = angular.module('myApp', []);
myApp.controller("caseCtrl", function ($scope, $http, $routeParams, $timeout) {
$scope.caseData = null;
//mimic a delay in getting the data from $http
$timeout(function () {
$scope.caseData = 'hey!';
}, 1000);
})
.directive('showHide', function () {
return {
link: function (scope, element, attributes, controller) {
scope.$watch(attributes.showHide, function (v) {
if (v) {
element.show();
} else {
element.hide();
}
});
}
};
});
HTML
<div ng-controller='caseCtrl' show-hide='caseData'>using directive</div>
<div ng-controller='caseCtrl' ng-show='caseData'>using ngShow</div>
JSFIDDLE:http://jsfiddle.net/mac1175/zzwBS/
Since you asked for a directive, try this.
.directive('showOnLoad', function() {
return {
restrict: 'A',
link: function($scope,elem,attrs) {
elem.hide();
$scope.$on('show', function() {
elem.show();
});
}
}
});
Stick (show-on-load) in your element, and in your controller inject $rootScope, and use this broadcast event when the html has loaded.
$rootScope.$broadcast('show');
I have used Zack's response to create a 'loading' directive, which might be useful to some people.
Template:
<script id="ll-loading.html" type="text/ng-template">
<div layout='column' layout-align='center center'>
<md-progress-circular md-mode="indeterminate" value="" md-diameter="52"></md-progress-circular>
</div>
</script>
Directive:
directives.directive('loading', function() {
return {
restrict: 'E',
template: 'll-loading.html',
link: function($scope,elem,attrs) {
elem.show();
$scope.$on('loaded', function() {
console.log("loaded: ");
elem.hide();
});
}
}
});
This example uses angular-material in the html
The accepted answer didn't work for me. I had some elements that had ng-show directives and the elements would still show momentarily even with the ng-cloak. It appears that the ng-cloak was resolved before the ng-show returned false. Adding the ng-hide class to my elements fixed my issue.

Resources