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!
Related
I am trying to inject services to the below directive which is used link function instead of controller.
(function() {
angular
.module("myApp")
.directive('myDirective',['$scope','myService','myData',function($scope,myService,myData) {
return {
restrict'AE',
replace:'true',
templateUrl :'/myApp/directive/my-directive',
scope: {
userId: "#"
}
},
link:function(scope,elem,attr)
{
//Code for link function goes here ...
scope.myUsersArray [];
scope.getUserDetails = function()
{
//code for getUserdetails goes here...
}
}
}]);
})();
When i run this code, i am getting exception like [$injector:unpr] Unkown Provider <_ $scope error. If i remove the injected services, i am not getting any error. Error throws at angular.js file so i don't have any clue to fix it :(
You have several syntax issues. Your code should look like this:
.directive('myDirective', ['$scope', 'myService', 'myData', function($scope, myService, myData) {
return {
restrict: 'AE',
replace: 'true',
templateUrl: '/myApp/directive/my-directive',
scope: {
userId: "#"
},
link: function(scope, elem, attr) {
//Code for link function goes here ...
scope.myUsersArray = [];
scope.getUserDetails = function() {
//code for getUserdetails goes here...
};
}
};
}]);
You may have more problems as its not evident as to why you are injecting $scope, myService, and myData
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>
I'm running into that annoying Angular minify problem (I really hope this issue is non-existent in Angular 2)
I've commented out all my app module injections and going down the list 1 by 1 to find out where the problem is and I think I narrowed it down to my searchPopoverDirectives:
Can you see what I'm doing wrong?
Original code, produces this error Unknown provider: eProvider <- e:
(function() { "use strict";
var app = angular.module('searchPopoverDirectives', [])
.directive('searchPopover', function() {
return {
templateUrl : "popovers/searchPopover/searchPopover.html",
restrict : "E",
scope : false,
controller : function($scope) {
// Init SearchPopover scope:
// -------------------------
var vs = $scope;
vs.searchPopoverDisplay = false;
}
}
})
})();
I then tried the [] syntax in an attempt to fix the minify problem and ran into this error Unknown provider: $scopeProvider <- $scope <- searchPopoverDirective:
(function() { "use strict";
var app = angular.module('searchPopoverDirectives', [])
.directive('searchPopover', ['$scope', function($scope) {
return {
templateUrl : "popovers/searchPopover/searchPopover.html",
restrict : "E",
scope : false,
controller : function($scope) {
// Init SearchPopover scope:
// -------------------------
var vs = $scope;
vs.searchPopoverDisplay = false;
}
}
}])
})();
UPDATE:
Also found out this guy is causing a problem:
.directive('focusMe', function($timeout, $parse) {
return {
link: function(scope, element, attrs) {
var model = $parse(attrs.focusMe);
scope.$watch(model, function(value) {
if (value === true) {
$timeout(function() {
element[0].focus();
});
}
});
element.bind('blur', function() {
scope.$apply(model.assign(scope, false));
})
}
}
})
When you minify code, it minify all code, so your
controller : function($scope) {
was minified to something like
controller : function(e) {
so, just use
controller : ["$scope", function($scope) { ... }]
When minifying javascript the parameter names are changed but strings remain the same.
You have 2 ways that you can define which services need to be injected:
Inline Annotation:
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http){
...
}]);
Using the $inject property:
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
PhoneListCtrl.$inject = ['$scope', '$http'];
function PhoneListCtrl($scope, $http){
...
}
Using $inject is considered more readable than inline annotations. It is best practice to always have one line declaring the controller, service, or directive, one line for defining the injection values, and finally the implementation method. The method may be defined last due to the hoisting nature of javascript.
Remember: The order of your annotation (strings) and your function parameters must be the same!
It's a well known issue in angular to need to use the special array syntax when bringing in dependencies into controllers to avoid minification problems, so I have been using that notation. But it seems that the injector is still having issues with this code that appear only after sending it through gulp-uglify.
Do other angular elements like directives also need to have this syntax be used? Also, I'm using object notation to define one of the controllers, so might that be the problem?
Some main config stuff.
var app = angular.module('musicApp', ['ngSanitize']);
//Whitelist Soundcloud
app.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://w.soundcloud.com/**'
]);
});
Directives, one with a controller in it.
app.directive('soundcloudHtml', ['$sce', function($sce){
return {
restrict: 'A',
link: function(scope, element, attrs) {
scope.musicPiece.soundcloud = $sce.trustAsHtml(scope.musicPiece.soundcloud);
}
}
}]);
app.directive('music', function(){
return {
restrict: 'E',
scope:{
type: '='
},
templateUrl: '/resources/data/music/music.html?po=343we',
link: function(scope, element, attrs) {
},
controller: ['$http', '$scope', function($http, $scope){
this.musicList = [];
$scope.Utils = Utils;
var ctrl = this;
$http.get('/resources/data/music/music.json').success(function(data){
ctrl.musicList = data;
Utils.updateTableOfContents();
});
}],
controllerAs: 'musicCtrl'
};
});
Looks like I missed that config also needs that pattern as well in order to be minified.
The config should be
//Whitelist Soundcloud
app.config(['$sceDelegateProvider', function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://w.soundcloud.com/**'
]);
}]);
And not
//Whitelist Soundcloud
app.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://w.soundcloud.com/**'
]);
});
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
//}]);