diff --git a/angular-meteor/angular-meteor-tests.ts b/angular-meteor/angular-meteor-tests.ts
new file mode 100644
index 000000000..be75f196b
--- /dev/null
+++ b/angular-meteor/angular-meteor-tests.ts
@@ -0,0 +1,253 @@
+///
+
+interface ITodo {
+ _id?: string;
+ name: string;
+ public?: boolean;
+ sticky?: boolean;
+}
+
+interface TodoAngularMeteorObject extends ITodo, angular.meteor.AngularMeteorObject {}
+
+interface CustomScope extends angular.meteor.IScope {
+ sticky: boolean;
+
+ todos: angular.meteor.AngularMeteorCollection;
+ stickyTodos: angular.meteor.AngularMeteorCollection;
+ notAutoTodos: angular.meteor.AngularMeteorCollection;
+
+ todo: ITodo;
+ todoNotAuto: TodoAngularMeteorObject;
+
+ save: (todo: ITodo) => void;
+ saveAll: () =>void;
+ autoSave: (todo: ITodo) => void;
+ remove: (todoId: string) => void;
+ removeAll: () => void;
+ removeAuto: (todo: ITodo) => void;
+ toSticky: (todo: ITodo) => void;
+}
+
+var Todos = new Mongo.Collection('todos');
+
+var app = angular.module('angularMeteorTestApp');
+
+app.controller("mainCtrl", ['$scope', '$meteor', ($scope: CustomScope, $meteor: angular.meteor.IMeteorService) => {
+ // Bind all the todos to $scope.todos
+ $scope.todos = $meteor.collection(Todos);
+
+ $scope.sticky = true;
+ // Bind all sticky todos to $scope.stickyTodos
+ // Binds the query to $scope.sticky so that if it changes, Meteor will re-run the query and bind it
+ // to $scope.stickyTodos
+ $scope.stickyTodos = $meteor.collection(function(){
+ return Todos.find({sticky: $scope.getReactively('sticky')});
+ });
+
+ // Bind without auto-save all todos to $scope.notAutoTodos
+ $scope.notAutoTodos = $meteor.collection(Todos, false).subscribe("publicTodos");
+
+ $scope.todoNotAuto = $meteor.object(Todos, 'TodoID', false);
+ $scope.todo = $scope.todoNotAuto.getRawObject();
+ $scope.todoNotAuto.reset();
+ $scope.todoNotAuto.save($scope.todo).then((data) => { return data == 1; });;
+
+ // todo might be an object like this {text: "Learn Angular", sticky: false}
+ // or an array like this:
+ // [{text: "Learn Angular", sticky: false}, {text: "Hello World", sticky: true}]
+
+ $scope.save = function(todo) {
+ $scope.notAutoTodos.save(todo);
+ };
+
+ $scope.saveAll = function() {
+ $scope.notAutoTodos.save();
+ };
+
+ $scope.autoSave = function(todo) {
+ $scope.todos.push(todo);
+ };
+
+ // todoId might be an string like this "WhrnEez5yBRgo4yEm"
+ // or an array like this ["WhrnEez5yBRgo4yEm","gH6Fa4DXA3XxQjXNS"]
+ $scope.remove = function(todoId) {
+ $scope.notAutoTodos.remove(todoId);
+ };
+
+ $scope.removeAll = function() {
+ $scope.notAutoTodos.remove();
+ };
+
+ $scope.removeAuto = function(todo) {
+ $scope.todos.splice( $scope.todos.indexOf(todo), 1 );
+ }
+
+ $scope.toSticky = function(todo) {
+ if (angular.isArray(todo)){
+ angular.forEach(todo, function(object) {
+ object.sticky = true;
+ });
+ } else {
+ todo.sticky = true;
+ }
+
+ $scope.stickyTodos.save(todo);
+ };
+
+ var todoObject = {name:'first todo'};
+ var todosArray = [{name:'second todo'}, {name:'third todo'}];
+ var todoSecondObject = {name:'forth todo'};
+
+ $scope.todos.save(todoObject); // todos equals [{name:'first todo'}]
+
+ $scope.todos.save(todosArray); // todos equals [{name:'first todo'}, {name:'second todo'}, {name:'third todo'}]
+
+ $scope.todos.push(todoSecondObject); // The scope variable equals to [{name:'first todo'}, {name:'second todo'}, {name:'third todo'}, {name:'forth todo'}]
+ // but the collection still equals to [{name:'first todo'}, {name:'second todo'}, {name:'third todo'}]
+
+ $scope.todos.save(); // Now the collection also equals to [{name:'first todo'}, {name:'second todo'}, {name:'third todo'}, {name:'forth todo'}]
+
+ $scope.todos.remove('firstTodoId'); // scope and collection equals to [{name:'second todo'}, {name:'third todo'}, {name:'forth todo'}]
+
+ var todoIdsArray = ['secondTodoId', 'thirdTodoId'];
+ $scope.todos.remove(todoIdsArray); // removes everything matches the array of IDs both in scope and in collection
+
+ $scope.todos.pop(); // removes only in scope
+
+ $scope.todos.remove(); // syncs also in Meteor collection
+
+ // Subscribe ->
+
+ $meteor.subscribe('todos').then((subscriptionHandle) => {
+ // Bind all the todos to $scope.todos
+ $scope.todos = $meteor.collection(Todos);
+
+ console.log($scope.todos + ' is ready');
+
+ // You can use the subscription handle to stop the subscription if you want
+ subscriptionHandle.stop();
+ });
+
+ $scope.subscribe('todos').then((subscriptionHandle) => {
+ // Bind all the todos to $scope.books
+ $scope.todos = $meteor.collection(Todos);
+
+ console.log($scope.todos + ' is ready');
+
+ // No need to stop the subscription, it will automatically close on scope destroy
+ });
+
+ $meteor.call('subscribe', $scope.todo._id, $scope.currentUser._id).then((data) => {
+ // Handle success
+ console.log('success subscribing', data.name);
+ }, (err) => {
+ // Handle error
+ console.log('failed', err);
+ });
+
+ if (!$scope.loggingIn) {
+ $meteor.waitForUser();
+
+ $meteor.requireUser();
+
+ $meteor.requireValidUser(user => {
+ return user.username == 'admin';
+ });
+
+ $meteor.loginWithPassword('user', 'password').then(() => {
+ console.log('Login success');
+ }, err => {
+ console.log('Login error - ', err);
+ });
+
+ $meteor.createUser({
+ username:'moma',
+ email:'example@gmail.com',
+ password: 'Bksd@asdf',
+ profile: {expertize: 'Developer'}
+ }).then(() => {
+ console.log('Login success');
+ }, err => {
+ console.log('Login error - ', err);
+ });
+
+ $meteor.changePassword('old', 'new232f3').then(() => {
+ console.log('Change password success');
+ }, err => {
+ console.log('Error changing password - ', err);
+ });
+
+ $meteor.forgotPassword({email: 'sample@gmail.com'}).then(() => {
+ console.log('Success sending forgot password email');
+ }, err => {
+ console.log('Error sending forgot password email - ', err);
+ });
+
+ $meteor.resetPassword('tokenID', 'new232f3').then(() => {
+ console.log('Reset password success');
+ }, err => {
+ console.log('Error resetting password - ', err);
+ });
+
+ $meteor.verifyEmail('tokenID').then(() => {
+ console.log('Success verifying password ');
+ }, err => {
+ console.log('Error verifying password - ', err);
+ });
+
+ $meteor.logout().then(() => {
+ console.log('Logout success');
+ }, err => {
+ console.log('logout error - ', err);
+ });
+
+ $meteor.logoutOtherClients().then(() => {
+ console.log('Logout success');
+ }, err => {
+ console.log('logout error - ', err);
+ });
+
+ var loginWithOptions = {requestPermissions: ['email']};
+
+ $meteor.loginWithFacebook({requestPermissions: ['email']}).then(() => {
+ console.log('Login success');
+ }, err => {
+ console.log('Login error - ', err);
+ });
+ $meteor.loginWithGithub({requestPermissions: ['email']}).then(() => {
+ console.log('Login success');
+ }, err => {
+ console.log('Login error - ', err);
+ });
+ $meteor.loginWithGoogle({requestPermissions: ['email']}).then(() => {
+ console.log('Login success');
+ }, err => {
+ console.log('Login error - ', err);
+ });
+ $meteor.loginWithMeetup({requestPermissions: ['email']}).then(() => {
+ console.log('Login success');
+ }, err => {
+ console.log('Login error - ', err);
+ });
+ $meteor.loginWithTwitter({requestPermissions: ['email']}).then(() => {
+ console.log('Login success');
+ }, err => {
+ console.log('Login error - ', err);
+ });
+ $meteor.loginWithWeibo({requestPermissions: ['email']}).then(() => {
+ console.log('Login success');
+ }, err => {
+ console.log('Login error - ', err);
+ });
+ }
+
+ $meteor.autorun($scope, () => { console.log("Aurorun triggered."); });
+ $meteor.getCollectionByName('collectionName');
+
+ // requires meteor add mdg:camera
+ $meteor.getPicture().then(function(data){
+ $scope['picture'] = data;
+ });
+
+ $meteor.session('counter').bind($scope, 'counter');
+}]);
diff --git a/angular-meteor/angular-meteor.d.ts b/angular-meteor/angular-meteor.d.ts
new file mode 100644
index 000000000..18f2e033e
--- /dev/null
+++ b/angular-meteor/angular-meteor.d.ts
@@ -0,0 +1,322 @@
+// Type definitions for Angular JS Meteor v0.8.8 (angular.meteor module)
+// Project: https://github.com/Urigo/angular-meteor
+// Definitions by: Peter Grman
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+///
+///
+
+declare module angular.meteor {
+ interface IRootScopeService extends angular.IRootScopeService {
+ /**
+ * The current logged in user and it's data. it is null if the user is not logged in. A reactive data source.
+ */
+ currentUser: Meteor.User;
+
+ /**
+ * True if a login method (such as Meteor.loginWithPassword, Meteor.loginWithFacebook, or Accounts.createUser) is currently in progress.
+ * A reactive data source. Can be use to display animation while user is logging in.
+ */
+ loggingIn: boolean;
+ }
+
+ interface IScope extends angular.IScope, IRootScopeService {
+ /**
+ * A method to get a $scope variable and watch it reactivly
+ *
+ * @param scopeVariableName - The name of the scope's variable to bind to
+ * @param [objectEquality=false] - Watch the object equality using angular.equals instead of comparing for reference equality, deeper watch but also slower
+ */
+ getReactively(scopeVariableName: string, objectEquality?: boolean): ReactiveResult;
+
+ /**
+ * A service which is a wrapper for Meteor.subscribe. It subscribes to a Meteor.publish method in the client and returns a AngularJS promise when ready.
+ * Calling $scope.subscribe will automatically stop the subscription when the scope is destroyed.
+ *
+ * @param name - Name of the subscription. Matches the name of the server's publish() call.
+ * @param publisherArguments - Optional arguments passed to publisher function on server.
+ *
+ * @return The promise solved successfully when subscription is ready. The success promise holds the subscription handle.
+ */
+ subscribe(name: string, ...publisherArguments: any[]): angular.IPromise;
+ }
+
+ /**
+ * $meteor in angularjs
+ */
+ interface IMeteorService {
+ /**
+ * A service that wraps the Meteor collections to enable reactivity within AngularJS.
+ *
+ * @param collection - A Meteor Collection or a reactive function to bind to.
+ * - Reactive function can be used with $scope.getReactively to add $scope variable as reactive variable to the cursor.
+ * @param [autoClientSave=true] - By default, changes in the Angular collection will automatically update the Meteor collection.
+ * - However if set to false, changes in the client won't be automatically propagated back to the Meteor collection.
+ */
+ collection(collection: Mongo.Collection|ReactiveResult, autoClientSave?: boolean): AngularMeteorCollection;
+
+ /**
+ * A service that wraps the Meteor collections to enable reactivity within AngularJS.
+ *
+ * @param collection - A Meteor Collection or a reactive function to bind to.
+ * - Reactive function can be used with $scope.getReactively to add $scope variable as reactive variable to the cursor.
+ * @param [autoClientSave=true] - By default, changes in the Angular collection will automatically update the Meteor collection.
+ * - However if set to false, changes in the client won't be automatically propagated back to the Meteor collection.
+ * @param [updateCollection] - A collection object which will be used for updates (insert, update, delete).
+ */
+ collection(collection: Mongo.Collection|ReactiveResult, autoClientSave: boolean, updateCollection: Mongo.Collection): AngularMeteorCollection2;
+
+ /**
+ * A service that wraps a Meteor object to enable reactivity within AngularJS.
+ * Finds the first document that matches the selector, as ordered by sort and skip options. Wraps collection.findOne
+ *
+ * @param collection - A Meteor Collection to bind to.
+ * @param selector - A query describing the documents to find or just the ID of the document.
+ * - $meteor.object will find the first document that matches the selector,
+ * - as ordered by sort and skip options, exactly like Meteor's collection.findOne
+ * @param [autoClientSave=true] - By default, changes in the Angular object will automatically update the Meteor object.
+ * - However if set to false, changes in the client won't be automatically propagated back to the Meteor object.
+ */
+ object(collection: Mongo.Collection, selector: Mongo.Selector|Mongo.ObjectID|string, autoClientSave?: boolean): AngularMeteorObject;
+
+ /**
+ * A service which is a wrapper for Meteor.subscribe. It subscribes to a Meteor.publish method in the client and returns a AngularJS promise when ready.
+ *
+ * @param name - Name of the subscription. Matches the name of the server's publish() call.
+ * @param publisherArguments - Optional arguments passed to publisher function on server.
+ *
+ * @return The promise solved successfully when subscription is ready. The success promise holds the subscription handle.
+ */
+ subscribe(name: string, ...publisherArguments: any[]): angular.IPromise;
+
+ /**
+ * A service service which wraps up Meteor.methods with AngularJS promises.
+ *
+ * @param name - Name of method to invoke
+ * @param methodArguments - Optional method arguments
+ *
+ * @return The promise solves successfully with the return value of the method or return reject with the error from the method.
+ */
+ call(name: string, ...methodArguments: any[]): angular.IPromise;
+
+ // User Authentication BEGIN ->
+
+ /**
+ * Returns a promise fulfilled with the currentUser when the user subscription is ready.
+ * This is useful when you want to grab the current user before the route is rendered.
+ * If there is no logged in user, it will return null.
+ * See the “Authentication with Routers” section of our tutorial for more information and a full example.
+ */
+ waitForUser(): angular.IPromise;
+
+ /**
+ * Resolves the promise successfully if a user is authenticated and rejects otherwise.
+ * This is useful in cases where you want to require a route to have an authenticated user.
+ * You can catch the rejected promise and redirect the unauthenticated user to a different page, such as the login page.
+ * See the “Authentication with Routers” section of our tutorial for more information and a full example.
+ */
+ requireUser(): angular.IPromise;
+
+ /**
+ * Resolves the promise successfully if a user is authenticated and the validatorFn returns true; rejects otherwise.
+ * This is useful in cases where you want to require a route to have an authenticated user and do extra validation like the user's role or group.
+ * You can catch the rejected promise and redirect the unauthenticated user to a different page, such as the login page.
+ * See the “Authentication with Routers” section of our tutorial for more information and a full example.
+ *
+ * The mandatory validator function will be called with the authenticated user as the single param and it's expected to return true in order to resolve.
+ * If it returns a string, the promise will be rejected using said string as the reason.
+ * Any other return (false, null, undefined) will be rejected with the default "FORBIDDEN" reason.
+ */
+ requireValidUser(validatorFn: (user: Meteor.User) => boolean|string): angular.IPromise;
+
+ /**
+ * Log the user in with a password.
+ *
+ * @param user - Either a string interpreted as a username or an email; or an object with a single key: email, username or id.
+ * @param password - The user's password.
+ */
+ loginWithPassword(user: string|{email: string}|{username: string}|{id: string}, password: string): angular.IPromise;
+
+ /**
+ * Create a new user. More information: http://docs.meteor.com/#/full/accounts_createuser
+ *
+ * @param options.username - A unique name for this user. Either this, or email is required.
+ * @param options.email - The user's email address. Either this, or username is required.
+ * @param options.password - The user's password. This is not sent in plain text over the wire.
+ * @param options.profile - The user's profile, typically including the name field.
+ */
+ createUser(options: {username?: string; email?: string; password: string; profile?: Object}): angular.IPromise;
+
+ /**
+ * Change the current user's password. Must be logged in.
+ *
+ * @param oldPassword - The user's current password. This is not sent in plain text over the wire.
+ * @param newPassword - A new password for the user. This is not sent in plain text over the wire.
+ */
+ changePassword(oldPassword: string, newPassword: string): angular.IPromise;
+
+ /**
+ * Request a forgot password email.
+ *
+ * @param options.email - The email address to send a password reset link.
+ */
+ forgotPassword(options: {email: string}): angular.IPromise;
+
+ /**
+ * Reset the password for a user using a token received in email. Logs the user in afterwards.
+ *
+ * @param token - The token retrieved from the reset password URL.
+ * @param newPassword - A new password for the user. This is not sent in plain text over the wire.
+ */
+ resetPassword(token: string, newPassword: string): angular.IPromise;
+
+ /**
+ * Marks the user's email address as verified. Logs the user in afterwards.
+ *
+ * @param token - The token retrieved from the reset password URL.
+ */
+ verifyEmail(token: string): angular.IPromise;
+
+ loginWithFacebook: ILoginWithExternalService;
+ loginWithTwitter: ILoginWithExternalService;
+ loginWithGoogle: ILoginWithExternalService;
+ loginWithGithub: ILoginWithExternalService;
+ loginWithMeetup: ILoginWithExternalService;
+ loginWithWeibo: ILoginWithExternalService;
+
+ /**
+ * Log the user out.
+ *
+ * @return Resolves with no arguments on success, or reject with a Error argument on failure.
+ */
+ logout(): angular.IPromise;
+
+ /**
+ * Log out other clients logged in as the current user, but does not log out the client that calls this function.
+ * For example, when called in a user's browser, connections in that browser remain logged in,
+ * but any other browsers or DDP clients logged in as that user will be logged out.
+ *
+ * @return Resolves with no arguments on success, or reject with a Error argument on failure.
+ */
+ logoutOtherClients(): angular.IPromise;
+
+ // <- User Authentication END
+ // $meteorUtils BEGIN ->
+
+ /**
+ * @param scope - The AngularJS scope you use the autorun on.
+ * @param fn - The function that will re-run every time a reactive variable changes inside it.
+ */
+ autorun(scope: angular.IScope, fn: Function): void;
+
+ /**
+ * @param collectionName - The name of the collection you want to get back
+ */
+ getCollectionByName(collectionName: string): Mongo.Collection;
+
+ // <- $meteorUtils END
+ // $meteorCamera BEGIN ->
+
+ /**
+ * A helper service for taking pictures across platforms.
+ * Must add mdg:camera package to use! (meteor add mdg:camera)
+ *
+ * @param [options] - options is an optional argument that is an Object with the following possible keys:
+ * @param options.width - An integer that specifies the minimum width of the returned photo.
+ * @param options.height - An integer that specifies the minimum height of the returned photo.
+ * @param options.quality - A number from 0 to 100 specifying the desired quality of JPEG encoding.
+ *
+ * @return The promise solved successfully when the picture is taken with the data as a parameter or rejected with an error as a parameter in case of error.
+ */
+ getPicture(options?: {width?: number; height?: number; quality?: number}): angular.IPromise;
+
+ // <- $meteorCamera END
+
+ /**
+ * A service that binds a scope variable to a Meteor Session variable.
+ *
+ * @param sessionKey - The name of the session variable
+ * @return An object with a single function bind - to bind to that variable.
+ */
+ session(sessionKey: string): {
+ /**
+ * @param scope - The scope the document will be bound to.
+ * @param model - The name of the scope's model variable that the document will be bound to.
+ */
+ bind: (scope: IScope, model: string) => void;
+ };
+ }
+
+ /**
+ * An object that connects a Meteor Object to an AngularJS scope variable.
+ *
+ * The object contains also all the properties from the generic type T,
+ * unfortunately TypeScript doesn't at the moment allow to extend a generic type (see https://github.com/Microsoft/TypeScript/issues/2225 for details and updates).
+ * For a workaround, you'll need to implement an interface which will merge AngularMeteorObject together with T and cast it, like this:
+ *
+ * interface TodoAngularMeteorObject extends ITodo, AngularMeteorObject { }
+ * var todo = $meteor.object(TodoCollection, 'TodoID');
+ */
+ interface AngularMeteorObject {
+ /**
+ * @param [doc] - The doc to save to the Meteor Object. If nothing is passed, the method saves everything in the AngularMeteorObject as is.
+ * - Unchanged properties will be overridden with their existing values, which may trigger hooks.
+ * - If doc is passed, the method only updates the Meteor Object with the properties passed, and no other changes will be saved.
+ *
+ * @return Returns a promise with an error in case for an error or a number of successful docs changed in case of success.
+ */
+ save(doc?: T): angular.IPromise;
+
+ /**
+ * Reset the current value of the object to the one in the server.
+ */
+ reset(): void;
+
+ /**
+ * Returns a copy of the AngularMeteorObject with all the AngularMeteor-specific internal properties removed.
+ * The returned object is then safe to use as a parameter for method calls, or anywhere else where the data needs to be converted to JSON.
+ */
+ getRawObject(): T;
+ }
+
+ /**
+ * An object that connects a Meteor Collection to an AngularJS scope variable
+ */
+ interface AngularMeteorCollection extends AngularMeteorCollection2 { }
+
+ /**
+ * An object that connects a Meteor Collection to an AngularJS scope variable,
+ * but can use a differen type for updates.
+ */
+ interface AngularMeteorCollection2 extends Array {
+ /**
+ * @param [docs] - The docs to save to the Meteor Collection.
+ * - If the docs parameter is empty, the method saves everything in the AngularMeteorCollection as is.
+ * - If an object is passed, the method pushes that object into the AngularMeteorCollection.
+ * - If an array is passed, the method pushes all objects in the array into the AngularMeteorCollection.
+ */
+ save(docs?: U|U[]): void;
+
+ /**
+ * @param [keys] - The keys of the object to remove from the Meteor Collection.
+ * - If nothing is passed, the method removes all the documents from the AngularMeteorCollection.
+ * - If an object is passed, the method removes the object with that key from the AngularMeteorCollection.
+ * - If an array is passed, the method removes all objects that matches the keys in the array from the AngularMeteorCollection.
+ */
+ remove(keys?: string|number|string[]|number[]): void;
+
+ /**
+ * A shorten (Syntactic sugar) function for the $meteor.subscribe function.
+ * Takes only one parameter and not returns a promise like $meteor.subscribe does.
+ *
+ * @param subscriptionName - The subscription name to subscribe to. Exactly like the first parameter in $meteor.subscribe service.
+ */
+ subscribe(subscriptionName:string): AngularMeteorCollection2;
+ }
+
+ interface ILoginWithExternalService {
+ (options: Meteor.LoginWithExternalServiceOptions): angular.IPromise;
+ }
+
+ interface ReactiveResult { }
+}