So I am using google maps api and the library provides me with this event:
google.maps.event.addListener(map, 'zoom_changed', function() {
This works if I just put it in my JS file, but how does that translate in a View with Backbone.js? How I implement this with the framework? I tried with .on in the initialize function, but it does not seem to work
Google map api events aren't DOM events, so you don't wire them up the same way in your view. I just put my google addListener calls inside the initialize function of whatever view is relevant and call any additional methods from inside the callback function you provide to the addListener call (just be sure to save a reference to this first so you can properly call any other view methods in there.)
example:
var MapView = Backbone.View.extend({
initialize: function () {
var self = this;
// assuming that map variable is defined here, otherwise pass in a reference to it through view options
map = new google.maps.Map(blah blah blah);
google.maps.event.addListener(map, 'zoom_changed', function () {
self.handleZoomChanged();
});
},
handleZoomChanged: function () {
// do whatever here
}
};
Related
I have an angular component that is reused multiple time in the same page.
The angular component is dependent on a service, since it is reused multiple times, I would like to have a new instance of a service for each component, is that possible? Or do I have to use a for loop and create a separate object inside the service for each component to achieve the same effect?
Edit: I am using angular 1
You can create a factory (instead of a service) which returns a function to which you can even provide some data (think of it as a constructor).
angular
.module('app')
.factory('MyFactory', MyFactory);
function MyFactory() {
return (someOptions) => ({
myProperty: someOptions.myProperty,
myMethod: () => {
// ...
}
});
}
You can than use it in your controller like so. Every time you call MyFactory({ ... }) a new separate "instance" will be created.
angular
.module('app')
.controller('MyController', MyController);
function MyController(MyFactory) {
const myFactoryObj = MyFactory({ myProperty: 'test' });
}
You can use that service in each of your components as DI and then call that service to create a new instance every time. You can also set 'cache':true in your function so that you don't make multiple api calls for the same data. (Only for 'GET' method). Does that clarify your doubt?
I am doing an app where when I click on a marker on a google.map triggers some action, specifically I want to get an object out of an array of objects to display the details of such object.
I use the controller defs like so
(function() {
'use strict';
angular
.module('gulpAngular')
.controller('GeolocationController', GeolocationController);
/** #ngInject */
function GeolocationController(HouseList) {
....
}
})();
The HouseList is a service defined elsewhere and having a method called getHouses()
Inside my controller, I do besides other things this:
var vm = this;
vm.houses = HouseList.getHouses();
Then I define my marker on the map like
allMarkers = new google.maps.Marker({....});
and add an Listener to it like below. To make things simple, I assign vm.house = vm.houses[0]
allMarkers.addListener('click', function() {
vm.house = vm.houses[0]
}
Now I suppose I should be able to use the vm.house object to display in my html block the attributes of this house object in the fashion of
<h4>{{vm.house.bedrooms}}</h4>
HOWEVER, NOTHING GETS DISPLAYED. I should see my vm.house object be updated and this reflected on the DOM, correct? What do I miss?
Funny: When i add a simple button on the html and use a ng-click function on it without anything other than say console.log(vm.house), not only it does display the correct object, but also the refresh of the html is happening. I am lost
thanks
Peter
addListener is not an Angular function and will not trigger the digest loop. You need to do it manually.
For example:
allMarkers.addListener('click', function() {
$scope.$apply(function () {
vm.house = vm.houses[0]
});
});
Note that you need to inject $scope for this.
ng-click triggers the digest loop, which is why using it will update the HTML.
I have an app that has items, and you can do things like add new items, update the text of an item, move the item to a different folder, etc.
I have an items factory that holds all the items as plain objects inside an array, and the factory returns a singleton that has various methods, like get(), set(), etc.
To add some context to the question, I'm working with Node.js and MongoDB as well.
Anyway, due to all the various factories I have, like items, folders, and all the various controllers for different views, I am relying heavily on events. To give some examples:
// items factory
update: function(params) {
// add to database, then...
.then(function() {
$rootScope.$emit('itemCreated');
});
}
// items controller
// I need to refresh the items list in the scope
$rootScope.$on('itemCreated', function() { // when an item is added to the database
$scope.items = items.getAll(); // retrieve all items from the items factory
});
These are their own kind of "subset" of events, in that they all pertain to "CRUD" operations on items.
But, I also have other events that I use. For example, I have an interceptor that listens to any requests and responses. I have a loading widget (an image of a spinning wheel) that uses a directive. This directive will show the loading widget when a request begins, and hide the loading widget when a request ends. This is also event based.
// on request
$rootScope.$emit(_START_REQUEST_);
// on any response
$rootScope.$emit(_END_REQUEST_);
I attempted to "modularize" these request and response events by simply making them constants.
.constant('_START_REQUEST_', '_START_REQUEST_');
I am trying to find a solution in order to "modularize" all my other events, like the events emitted on CRUD operations for items. One idea I've had is to define all of the item CRUD events inside the items factory:
events: {
update: 'itemUpdate',
create: 'itemCreated'
// etc.
}
Then, I can simply inject my items factory into a controller, and reference events like so:
$rootScope.$on(items.events.update, function() {});
I also considered simply defining all events, regardless of whether they are interceptor events or item events, as constants in my app. However, it seemed like this solution directly coupled item events to the module itself, rather than to the items factory, which is where I feel they "belong".
Basically, the issue is that right now all my events definitions seem to be scattered around. My question is: what pattern or best practice would you recommend for modularizing and defining events in AngularJS?
I agree that these item events should belong to the event source. You could implement a observer pattern in the item factory that hides the dependency on $rootScope for event listeners. This way the event key itself is a private detail of the item factory, and the subscription to the event is made explicit by calling a dedicated function for it. This approach makes your code more independent of $rootScope and easier to maintain than an event name convention (thinking about usages search for the specific event subscription method vs. usages of $rootScope.$emit / $on):
angular.module('events', [])
.service('items', ['$rootScope', function($rootScope) {
var createdEventKey = 'item.created';
return {
create: function () {
$rootScope.$emit(createdEventKey, {"name": "aItemName"});
},
onCreated: function(callback, scope) {
var unsubscribeFunction = $rootScope.$on(createdEventKey, function(event, payload) {
callback(payload);
});
// allow to unsubscribe automatically on scope destroy to prevent memory leaks
if (scope) {
scope.$on("$destroy", unsubscribeFunction);
}
return unsubscribeFunction;
}
}
}])
.controller('TestController', function($scope, items) {
items.onCreated(function (item) {
console.log("Created: " + item.name);
}, $scope);
});
complete example: http://jsfiddle.net/8LtyB/32/
If all you want is a way to create a separate object for containing the names of events, why not use a service?
myApp.service('itemEvents', function () {
var events = {
update: 'itemupdate',
create: 'itemcreate',
...
};
return events;
});
This is essentially what you had before when you were suggesting using a factory to contain the event definitions, except that a service is a single object instance, and is instantiated at module start-up. In contrast, a factory creates a new instance when injected into a controller. (Here's a good SO post on the difference between services and factories)
You can inject this service into your controllers or directives:
myApp.controller('ItemController', function ($scope, itemEvents) {
$scope.on(itemEvents.update, function () { /* something interesting */ });
});
This gives you a nice place to centralize your event name definitions. As a side note, some people hold to the convention of using all lowercase when defining event names (so itemupdate instead of itemUpdate). Hope this helps!
You can use the following:
app.config(function($provide) {
$provide.decorator("$rootScope", function($delegate) {
var Scope = $delegate.constructor;
var origBroadcast = Scope.prototype.$broadcast;
var origEmit = Scope.prototype.$emit;
Scope.prototype.$broadcast = function() {
console.log("$broadcast was called on $scope " + Scope.$id + " with arguments:",
arguments);
return origBroadcast.apply(this, arguments);
};
Scope.prototype.$emit = function() {
console.log("$emit was called on $scope " + Scope.$id + " with arguments:",
arguments);
return origEmit.apply(this, arguments);
};
return $delegate;
});
})
example: http://plnkr.co/edit/cn3MZynbpTYIcKUWmsBi?p=preview
src: https://github.com/angular/angular.js/issues/6043
assuming these $scope.$emit works like jquery events I would suggest you name your emits to be generic for example in you database update simply do this:
$rootScope.$emit('Created')
then in your items controller do this :
$rootScope.$on('Created.item', function() { // when an item is added to the database
$scope.items = items.getAll(); // retrieve all items from the items factory
});
then you can wire to the created event in any of your controllers and its name is generic. The .item should add a namespace. if you make all of your events in your items controller have the .item name space you should be able to do a
$rootScope.$off('item')
This will clear up memory leaks
The best way of handling Firebase in AngularJS surely has to be from within a service, so it's available to all Controllers across the App.
I just can't get it to work! ... I first tried using angularFire(new Firebase(url)), hoping I could bind to the service's scope, but Angular complains that it cannot $watch it.
So I tried angularFireCollection instead like this:
app.factory('myService', function myService(angularFireCollection) {
var url = 'https://myfirebase.firebaseio.com';
return {
getAll: function(path) {
var ref = angularFireCollection(new Firebase(url + '/' + path));
console.log(ref);
return ref;
},
...
};
});
However, the angularFireCollection is an Object containing a load of methods etc. if I bind it to a controller $scope I just get garbage. It also complains that it can't call the Firebase functions before I try to use them (e.g. Error: Firebase.push failed: second argument must be a valid function.)... anyone got any ideas where I'm going wrong?
See this PLUNKER
If you want to encapsulate some of the functionality into a service, consider keeping the returned ref in state of the service. I expanded on your plunker. It seems to mostly do what you were trying for.
http://plnkr.co/edit/Uf2fB0
Jeff answered the question correctly ... I'm just posting a further development on Jeff's example for those who are interested.
I have abstracted the Firebase service creation, so you can dynamically create an instance of whatever Firebase service you want:-
var registerFirebaseService = function (serviceName) {
app.factory(serviceName, function (angularFire) {
var _url = null;
var _ref = null;
return {
init: function (url) {
_url = url;
_ref = new Firebase(_url);
},
setToScope: function (scope, localScopeVarName) {
angularFire(_ref, scope, localScopeVarName);
}
};
});
};
You first create an instance of the service as follows
registerFirebaseService('itemsService'); // create itemsService instance
Then you can inject the itemsService service into your controllers. The instance is initialised using your Firebase URL e.g.
itemsService.init('https://firebase.firebaseio.com/' + userId + '/items');
The Firebase can now be bound to your controller e.g.
itemsService.setToScope($scope, 'items');
adapted PLUNKER
I have a div#game.
In here I want some data from the server to appear.
So when I use the initialize function I request data from my server in my game model with an ajax call.
Game = Backbone.View.extend(
{
id: 'game',
initialize: function ()
{
this.model.on('getGame', function() {});
this.model.getGame();
}
}
Because a callback doesn't seem to work in backbone a trigger has to be made.
So the app listens to the trigger getGame which is triggerd when the data from the server has been returned and saved into a variable in the model.
So far so good, this all works. The only problem now is that I want my div#game to fadeIn when it's done appending all data from the getGame function.
But I think because off the model.on(trigger) the initialize function 'thinks' it's ready after running the getGame() function, without actually having the data from the server appended yet.
So the parent div#all running the following:
this.$el.append(new Game().el);
also 'thinks' the div#game is ready and appends the div#game to itself and gives it a fadeIn.
div#game doesn't contain the server data yet, so when it actually does come back the images and text pop into existence instead of fadeIn in nicely...
Does some one know how to resolve this problem?
Thanks in advance!
======================================================
SOLVED
The problem was in the asynchronous property of the $.get and $.post functions. This is why the initialize was ready before the results had come back from the server. I changed it into a $.ajax and made the async: false. Now it all loads in the order I programmed/want.
Thank you for your detaild explaination Tallmaris, it will come in handy for sure since I'll be using a lot of triggers!
Rather than having the div#all render and append the view to itself, you can pass it as a parent to the View:
this.gameView = new Game({parent: this});
Then in your Game view:
initialize: function (options)
this.parent = options.parent
{
this.model.on('getGame', function() { this.$el.appendTo(this.parent); });
this.model.getGame();
}
Alternatively you could use a global Backbone event object like this:
window.vent = $.extend({}, Backbone.Events);
Now you have a global object (which you can name and put in any namespace as you like, don't just do it as in the code above), that you can use like this:
MAIN VIEW:
initialize: function () {
vent.on("GameReady", function ()
{
// fade in game view
});
}
GAME VIEW:
initialize: function (options)
this.parent = options.parent
{
this.model.on('getGame', function() { vent.trigger("GameReady"); });
this.model.getGame();
}
You can also pass parameters around. Read here for more documentation.