I want to embed a google map using an angular directive. The directive template runs a callback initMap which should call the scope initMap and create it, but am getting error:
InvalidValueError: initMap is not a function
How can I call the directive callback from the directive's HTML template?
Parent html file to display map:
<my-map></my-map>
Directive:
( function( ng, app ) {
'use strict';
app.directive( 'myMap', ['$rootScope', function($rootScope) {
return {
restrict: 'AE',
scope : {},
link: function(scope, element, attr) {
scope.Template = '/directive_html/map.html';
scope.initMap = function() {
var map = new google.maps.Map(document.getElementById('contact-map'), { ...
...
};
},
template: "<div ng-include='Template'></div>"
Map.html (directive template html)
<div id='contact-map'></div>
<script src="https://maps.googleapis.com/maps/api/js?callback=initMap" async defer></script>
<script>
// This is called
function initMap() {
// call directive scope.initMap()
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?callback=initMap"></script>
initMap needs to be a global function.
Before I continue with a possible solution, I would highly recommend using the angular-google-maps library.
However, you could do the following:
create initMap as a global function
have a mapLoadedService that returns a promise once gmaps initialised
resolve the promise in mapLoadedService on initMap
So add the following to your global js:
var initMap = function(){
var elem = angular.element(document.querySelector('[ng-app]')); //your app qs
var injector = elem.injector();
var mapLoadedService = injector.get('mapLoadedService');
mapLoadedService.loaded();
};
Add the following service to your angular app:
app.factory( 'mapLoadedService', ['$q', function($q) {
var defer = $q.defer();
this.init = function(){
return defer.promise;
};
this.loaded = function(){
defer.resolve();
};
});
then in your directive:
app.directive( 'myMap', ['$rootScope','mapLoadedService', function($rootScope, mapLoadedService) {
return {
restrict: 'AE',
scope : {},
link: function(scope, element, attr) {
scope.Template = '/directive_html/map.html';
//mapLoadedService returns a promise, that only resolves when google maps lib has loaded and is available globally...
mapLoadedService.init().then(function(){
var map = new google.maps.Map(document.getElementById('contact-map'), { });
});
},
template: "<div ng-include='Template'></div>"
}
});
You should also then load the google maps library in your index.html or app base.
<script src="https://maps.googleapis.com/maps/api/js?callback=initMap" async defer></script>
Related
I am having some problems testing an AngularJS (1.5) Attribute restricted Directive. See the below example directive and following unit test, of which produces a broken unit test.
Directive
(function (angular) {
'use strict';
function SomethingCtrl($filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelController) {
var exampleFilter = $filter('exampleFilter');
ngModelController.$parsers.push(function(value) {
ngModelController.$setViewValue(value);
ngModelController.$render();
return value;
});
}
};
}
SomethingCtrl.$inject = ['$filter'];
angular.module('something.formatter', [
'filters'
]).directive('somethingFormatter', SomethingCtrl);
}(window.angular));
Directive Unit Test
fdescribe('something.formatter spec', function () {
'use strict';
var scope,
element,
testValue,
compile,
ngModelCtrl;
beforeEach(function () {
module('something.formatter');
compile = function () {
inject([
'$compile',
'$rootScope',
function ($compile, $rootScope) {
scope = $rootScope.$new();
scope.testValue = testValue;
element = angular.element('<input something-formatter ng-model="testValue"');
$compile(element)(scope);
ngModelCtrl = element.controller('ngModel');
scope.$digest();
}
]);
};
});
describe('initialization', function () {
beforeEach(function () {
testValue = 'Yay!';
compile();
ngModelCtrl.$setViewValue('Nay?');
});
it('should be defined', function () {
expect(scope.testValue).toEqual('Nay?');
});
});
});
I tried following these instructions: http://jsfiddle.net/mazan_robert/hdsjbm9n/
To be able to call methods on the ngModelController, like; $setViewValue.
Yet, Jasmine continues to scream at me and tells me that $setViewValue is not a constructor, as well as doesn't even hit console.logs inside the actual directive.
TypeError: undefined is not an object (evaluating 'ngModelCtrl.$setViewValue')
Thoughts?
Thanks so much for your help!
It will work if you close the input tag:
element = angular.element('<input something-formatter ng-model="testValue" />');
So i got this component. and i can access the data that is passed to its bindings.
But only in it'template. I need to access the object in the component's own controller to do some stuff with it. And i am a bit stuck figuring it out.
Here is the component:
angular.module('MpWatchModule').component('mPointlite', {
bindToController: false,
restrict: 'E',
templateUrl: '/NG-MPWatch/Templates/mPointLite.html',
controller: function (NgMap) {
this.googleMapsUrl = 'https://maps.google.com/maps/api/js?key=<my_api_key>';
NgMap.getMap().then(function (map) {
this.map = map;
this.map.setMapTypeId('terrain');
// this.map.setMapTypeId('satellite');
this.mpObjs = mpdata;
});
},
controllerAs: 'mpl',
bindings: {
mpdata: '<',
},
});
And here is the markup in the components template:
<div map-lazy-load="https://maps.google.com/maps/api/js" map-lazy-load-params="{{mpl.googleMapsUrl}}">
<ng-map center="Hungary"
zoom="8"
class="gmap"
disable-default-u-i="true"
draggable-cursor="auto"
max-zoom="15"
min-zoom="8"
liteMode="true"
tilt="0">
<div ng-repeat="m in mpl.mpObjs">
<marker position="{{m.position}}">
</marker>
</div>
</ng-map>
</div>
Here is the markup from the page:
<m-pointlite mpdata="mpdl.mpObjs">
</m-pointlite>
And what i will need is to access the objects coming from the mpdl.mpObjs on the page. And do some stuff with them in the components controller, than display it in the components template. I accomplished most of it, just this missing link in the chain.
I appreciate anyone's help, and advise in advance.
Thanks
Remove bindToController: false
By default an angular component has bindToController set to true and allows you to access the bindings within the scope of the controller.
Then in your controller adjust the line:
this.mpObjs = mpdata;
to be this.mpObjs = this.mpdata;
I would suggest laying the code out like so just for better readability and easier to make changes/work with and follows the angular style guide:
(function () {
'use strict';
angular
.module('MpWatchModule')
.component('mPointlite', {
restrict: 'E',
bindings: {
mpdata: '<',
},
templateUrl: '/NG-MPWatch/Templates/mPointLite.html',
controller: PointLiteController,
controllerAs: 'mpl'
});
PointLiteController.$inject = ['NgMap'];
function PointLiteController(NgMap) {
var mpl = this;
mpl.googleMapsUrl = 'https://maps.google.com/maps/api/js?key=<my_api_key>';
activate();
function activate() {
NgMap.getMap().then(function (map) {
mpl.map = map;
mpl.map.setMapTypeId('terrain');
mpl.mpObjs = mpl.mpdata;
});
}
}
})();
I'm just putting the JS code alone. Try like below you will get the bindings inside controller
JS:
controller: function (NgMap) {
var ctrl = this;
this.googleMapsUrl = 'https://maps.google.com/maps/api/js?key=<my_api_key>';
NgMap.getMap().then(function (map) {
this.map = map;
this.map.setMapTypeId('terrain');
// this.map.setMapTypeId('satellite');
this.mpObjs = ctrl.mpdata;
});
}
I would like to ask you if it is possible, and if yes, then how can I pass some variable from controller to directive.
Here is a bit of my code:
app.js
var VirtualGallery = angular.module('virtualGallery', ['VirtualGalleryControllers', 'ngRoute']);
VirtualGallery.constant('apiURL', 'roomPicture');
VirtualGallery.run(['$rootScope', function ($rootScope) {
$rootScope.roomPictures = [];
}]);
var VirtualGalleryControllers = angular.module('VirtualGalleryControllers', ['ngRoute']);
VirtualGalleryControllers.controller('AppCtrl', function ($http, $rootScope, $scope, apiURL, $q) {
$scope.getallrooms = function () {
$http.get(apiURL)
.success(function (data) {
$rootScope.roomPictures = data; //idk whether to use $scope or $rootScope
});
};
});
In this app.js I'm trying to get some data from DB, and that data I need to use in directive.
Directive
angular.module('virtualGallery')
.directive('ngWebgl', function () {
return {
restrict: 'A',
scope: {
'getallrooms': '=',
'roomPictures': '='
},
link: function postLink(scope, element, attrs) {
scope.init = function () {
//here I would like to be able to access $scope or $rootScope from app.js file.
};
}
};
});
In directive I need to gain access to $scope or $rootScope in function init() where I need to use that data.
HTML
<body ng-app="virtualGallery">
<div class="container" ng-controller="AppCtrl">
<div
id="webglContainer"
ng-webgl
getallrooms="getallrooms"
roomPictures="roomPictures"
></div>
<p ng-model="roomPictures"></p>
<p ng-model="getallrooms"></p>
</div>
<script type="text/javascript" src="js/vg.js"></script>
<script type="text/javascript" src="js/ngWebgl.js"></script>
In html I'm trying to pass that data from app.js to directive.
Im quite new to Angular and this is even my first directive, so I am bit confused. Every help will be appreciated. Thanks guys :)
In your app.js use the controller like this
VirtualGalleryControllers.controller('AppCtrl', function ($http, $rootScope, $scope, apiURL, $q) {
$scope.getallrooms = function () {
$http.get(apiURL)
.success(function (data) {
$scope.roomPictures = data; //use $scope instead of $rootScope
});
};
});
Then for your directive:
angular.module('virtualGallery')
.directive('ngWebgl', function () {
return {
restrict: 'A',
scope: {
pictures: '=virtualGallery'
},
link: function postLink(scope, element, attrs) {
scope.init = function () {
// you can access the variable through the scope
scope.pictures;
};
}
};
});
Or you could simply make the http request in your directive and manipulate the data there.
You can inject $rootScope to your directive ( like you did in your controller ) and then access that rootScope variable.
I am rather new to angular, and trying to integrate it with google earth's javascript API.
I have done some work with on both, but never try to use them together. My motivation is to use the google earth module in an angular fashion, with controllers etc to manipulate the map.
http://www.ng-newsletter.com/posts/d3-on-angular.html
I want the google module to be available as with the d3 module in the link above, through a factory that loads the script. Then be able use the google module when the script is loaded.
I have gotten this to work with the d3 module:
angular.module('d3', [])
.factory('d3Service', ['$document', '$window', '$q', '$rootScope',
function($document, $window, $q, $rootScope) {
var d = $q.defer(),
d3service = {
d3: function() { return d.promise; }
};
function onScriptLoad() {
// Load client in the browser
$rootScope.$apply(function() { d.resolve($window.d3); });
}
var scriptTag = $document[0].createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.async = true;
scriptTag.src = 'http://d3js.org/d3.v3.min.js';
scriptTag.onreadystatechange = function () {
if (this.readyState == 'complete') onScriptLoad();
}
scriptTag.onload = onScriptLoad;
var s = $document[0].getElementsByTagName('body')[0];
s.appendChild(scriptTag);
return {
d3: function(){ return d.promise; }
};
}]);
Which then is available through directives:
.directive('d3Bars', ['$window', '$timeout', 'd3Service',
function($window, $timeout, d3Service) {
return {
restrict: 'A',
scope: {
data: '=',
label: '#',
onClick: '&'
},
link: function(scope, ele, attrs) {
d3Service.d3().then(function(d3) {
//code...
}}
}]);
I have tried to rewrite this to load the google loader JS module https://developers.google.com/loader/:
and then replacing "d3" with "google". and ofc the scripTag.src variable. Should this be correct?
function onScriptLoad() {
// Load client in the browser
$rootScope.$apply(function() { d.resolve($window.google); });
}
I then have a simple directive where I want to use the google JS object to load the earth api.
the "getApi"-module is similar to "d3service" in the d3 factory above.
.directive('mapFrame' ['$window', '$timeout', 'getApi',
function($window, $timeout, getApi) {
return {
restrict: 'E',
scope: {},
link: function(scope, elements, attrs) {
getApi.google().then(function(google) {
console.log(google);
google.load("earth", "1");
//more code..
});
}
}
}])
in the html-file:
<div map-frame></div>
This way of doing it works with d3. I loads the script into the DOM, and can display some charts, which doesn't seem to happen with the google module. Looks like the directive never invokes the factory, since the javascript is never loaded into the DOM..
What is it that I'am missing, or isn't it even possible ?
Thanks!
I'm trying to compile directive via angular service but unfortunately it doesn't work.
The idea is to show errors in popups.
I've modified $exceptionHandler service:
crm.factory('$exceptionHandler', function(popup) {
return function(exception) {
popup.open({message: exception});
}
});
The popup service looks like this:
crm.factory('popup', function ($document) {
return {
open: function (data) {
var injector = angular.element(document).injector(),
$compile = injector.get('$compile'),
template = angular.element('<popup></popup>');
// var ctmp = $compile(template.contents());
$compile(template.contents());
$document.find('body').append(template);
}
};
});
And I don't think that this was a good idea to hard code $compile service (but I haven't any ideas how to realize this in angular):
$compile = injector.get('$compile')
Popup directive:
crm.directive('popup', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/public/js/templates/common/popup.html',
link: function() {
console.log('link()');
},
controller: function () {
console.log('ctrl()');
}
};
});
May be there are some other ways to do this?
Thanks.
You can inject $compile directly into your service, also you're not quite using $compile correctly:
//commented alternative lines for allowing injection and minification since reflection on the minified code won't work
//crm.factory('popup', ['$document', '$compile', function ($document, $compile) {
crm.factory('popup', function ($document, $compile) {
return {
open: function (data) {
var template = angular.element('<popup></popup>'),
compiled = $compile(template);
$document.find('body').append(compiled);
}
};
});
//closing bracket for alternative definition that allows minification
//}]);