dwwoelfel / oneblog-brand-new

0 stars 1 forks source link

Angular Meteor 1.3, now closer to the standard Meteor API #246

Open dwwoelfel opened 4 years ago

dwwoelfel commented 4 years ago

I’m excited to announce the new 1.3 release for the Angular package. This version is a huge step for working with Angular in the most native and performant way inside Meteor. Here are some of the biggest changes we made in this release:

  1. Introducing a new concept called helpers that makes it easier to use standard Meteor APIs to manage your data.

  2. Removing autobind to encourage people to use best practices of event handling to get better performance and easier maintainability.

  3. Simplifying the package by removing non-core integration parts, like getCamera, which are now easier to use because of helpers.

The Angular package has become one of the most popular packages in Atmosphere and the new version will make the Angular Meteor community use exactly the same APIs as the rest of the community, increasing the collaboration across view technologies. The new API also makes it easier to share and migrate code and packages between Blaze and Angular.

This post will start with the highlights but towards the end it will dig down into all of the technical details and decisions for this release.

Give feedback

We’re constantly improving Angular Meteor, so please post your thoughts in the comments!

Also, I would love to get feedback on the implementation itself.

Try it out

To check out the new changes in action, you can clone the Socially app (the app built in the Angular Meteor tutorial), do the tutorial itself, or just continue reading this long post.

Features and Improvements

Helpers

A new helpers syntax, which lets you use exactly the same syntax as Blaze helpers with the same Meteor code inside. No more special Angular wrappers for Mongo Collections. it gives us better integration with the rest of the community packages using the same API. For example, no need for special code to use the CollectionFS package. also, it means an easy migration for Blaze developers - even though Meteor is supporting and developing an easy way for Blaze developers to migrate to React, we also wanted to do our part and offer an easy migration path for Blaze developers both to Angular 1.x and Angular 2.0. A migration tutorial will also be published soon

Read more about helpers here.

Reactive subscribe function

The subscribe function parameters are now automatically wrapped in autorun to make them reactive by default and the rest of the API is exactly like Meteor.subscribe.

Read more about subscribe here.

$reactive

All the new features use the $reactive service to make the reactivity abilities stem from one source.

Read more about $reactive here.

Diff Array

Until now we used our own implementation for diffing arrays and objects to change only the needed fields. Now we use an existing 3rd party solution.

Migrating your app to the new API

Although we are introducing a completely new API, we are leaving the old API until version 1.4 so you can gradually migrate your code. (but once we will remove the old code, you will see how little code we use right now compared to the old source).

Removing autobind

Instead of using autobind to update the scope from both directions, the $scope will be automatically updated from the server with helpers and the updates from the client will happen with the native Meteor API. We gain much better performance and easier maintainability both for the library and the apps developed with it.

$meteor.collection/$scope.$meteorCollection

There is no need for $meteor.collection anymore as with the helpers function we can use regular Mongo.Collections directly without any wrappers. Helpers will make sure to update Angular.

Old code:

$scope.parties = $scope.$meteorCollection(function(){ Parties.find({}, {sort: {createdAt: -1}}) });

New code:

$scope.helpers({ parties() { return Parties.find({}, {sort: {createdAt: -1}}) } });

Read more about that here.

$meteor.object/$scope.$meteorObject

There is no need for $meteor.object anymore as we can use Mongo Collection’s findOne function.

Old code:

$scope.party = $meteor.object(Parties, $stateParams.partyId);

New code:

$scope.helpers({ party() { return Parties.findOne($stateParams.partyId); }
});

Read more about that here.

$meteor.subscribe/$scope.$meteorSubscribe

We made some changes in the way we use subscribe. Usually when the subscription needs to get arguments that are reactive, you wrapped it with $meteorAutorun.

The new API wraps the subscriptions with Autorun for you, but you need to send the parameters inside an anonymous function:

Old code:

angular.module(`myApp`, []).controller(`MyCtrl`, function($scope) { $scope.myVar = `name`;
 $scope.meteorAutorun(function() { $scope.meteorSubscribe(`data`, $scope.getReactively(`myVar`), 10); }); });

New code:

angular.module('myApp', []).controller('MyCtrl', function($scope) {
 $scope.myVar = 'name'; $scope.subscribe('data', () => { return [ $scope.getReactively('myVar'), 10 ] }); // This now will trigger the subscription update! $scope.myVar = 'age'; });

Routing example — old code:

.state('tab', { url: '/tab', abstract: true, templateUrl: 'templates/tabs.html', resolve: { chats: ['$meteor', function ($meteor) { return $meteor.subscribe('chats'); }] } })

Routing example — new code:

.state('tab', { url: '/tab', abstract: true, templateUrl: 'templates/tabs.html', resolve: { chats: ['$q', function ($q) { var deferred = $q.defer(); Meteor.subscribe('chats', { onReady: deferred.resolve, onStop: deferred.reject }); return deferred.promise; }] } })

Read more about that here.

$meteor.call

Just call Meteor.call directly and work with the callback instead of the promise. Working with callback gives you the ability to close the subscription before it’s prepared, which you can’t do right now with the current Promise API. Also, if promise is a good idea, we should add it to Meteor and not just Angular Meteor.

Old code:

$meteor.call('invite', $scope.party._id, user._id).then( function(data){ console.log('success inviting', data); }, function(err){ console.log('failed', err); } );

New code:

Meteor.call('invite', $scope.party._id, user._id, function(error, result){ if (error) { console.log('failed', err); } else { console.log('success inviting', data); } });

Read more about that here.

$scope.getReactively

We improved `getReactively` to work on any context, both on `$scope` and on `this`

Old code:

$scope.parties = $scope.$meteorCollection(function(){ Parties.find({name : $scope.getReactively(“search”)}) }); $scope.search = “new search”; // Will trigger an update to `parties`

New code:

this.search = 'search';this.helpers({ parties() { Parties.find({name : this.getReactively('search')}) }, });
this.search = “new search”; // Will trigger an update to `parties`

Read more about that here.

$meteor.autorun / $scope.$meteorAutorun

In the previous version of Angular-Meteor we wrapped Meteor’s autorun method, but now we put that wrapper on your context (controllerAs or $scope).

Angular-Meteor will also automatically stop the autorun when the $scope is destroyed.

Old Code:

angular.module(`myApp`, []).controller(`MyCtrl`, function($scope) { $scope.meteorAutorun(function() {
 }); });

New Code:

Example using $scope:

angular.module(`myApp`, []).controller(`MyCtrl`, function($scope) { $scope.autorun(function() {
 }); });

Example using controllerAs and components:

angular.module(`myApp`, []).directive(`myComponent`, function() { return { restrict: `E`, controllerAs: `myCtrl`, controller: function($scope, $reactive) { $reactive(this).attach($scope); this.autorun(function() {
 }); } }; });

Example using angular2now and ReactiveComponent:

let {Component} = angular2now; angular.module(`myApp`); @Component({selector: `my-component`}) class myComponent extends ReactiveComponent { constructor() { super(arguments); this.autorun(function() {
}); } }

Read more about that here.

$meteor.collectionFS

No need for special wrapper anymore. In the previous version, we wrapped CollectionFS because we needed to manage it inside Angular-Meteor just like any other collection.

In 1.3, we removed the $meteor.collectionFS because we no longer need it — you can just create a helper for your collection that returns a cursor, just like any other collection.

Old code:

angular.module(`myApp`, []).controller(`MyCtrl`, function($scope) { $scope.myImages = $scope.meteorCollectionFs(Images); $scope.subscribe(`images`); $scope.myImages.save({ … }); $scope.imageUrl = $scope.myImages[0].url(); });

New code:

angular.module(`myApp`, []).controller(`MyCtrl`, function($scope) { $scope.subscribe(`images`); $scope.helpers({ myImages() { return Images.find({}); } }); $scope.imageUrl = $scope.myImages[0].url();
// This is the original Meteor API for using CollectionFS Images.insert({ … }); });

$meteor.getPicture — Removed

You can use the regular Meteor package. If you still want a wrapper for Meteor camera it should be in a separate package. Community packages are encouraged.

Read more about that here.

$meteor.session — Removed

Just like all other wrappers, we would like our users to use Meteor’s API, so instead of using unnecessary wrappers you can use the Meteor API directly.

Old Code:

angular.module(`myApp`, []).controller(`MyCtrl`, function($scope, $meteor) { $scope.myModel = 20; $meteor.session(`mySession`).bind($scope, `myModel`); });

New Code:

angular.module(`myApp`, []).controller(`MyCtrl`, function($scope) { Session.set(`mySession`, `myValue`); $scope.helpers({ myModel() { return Session.get(`mySession`); } }); });

Note that you are no longer be able to bind $scope to your session! if you are using sessions in order to get Reactive Vars, then it’s better that you will use reactive vars in scope with the new helpers syntax.

Read more about that here.

Accounts

Accounts is now no longer a part of Angular-Meteor core, so the following methods are no longer available:

Instead of these methods, you can just use Meteor’s API for those methods.

We will release a separate package with the existing functionality if anyone wants to keep using it. read more about that here.

For example, in the old version you needed to use $rootScope.currentUser in order to get the current logged in user, and now you can use Meteor.user() or Meteor.userId().

One of the usages of this wrapper was inside the view — $root.currentUser, now you will have to create an helper for that using the new API we provided, for example:

Example using controllerAs and components:

angular.module(`myApp`, []).directive(`myComponent`, function() { return { restrict: `E`, controllerAs: `myCtrl`, controller: function($scope, $reactive) { $reactive(this).attach($scope); this.helpers({ isLoggedIn() { return Meteor.userId() != null; }, currentUser() { return Meteor.user(); } }); } }; });

One other usage of the old API was to reject the state resolve phase using angular-ui-router, so now you need to wrap the Meteor method with a promise created by $q, but don’t worry because Angular provides a simple API for creating promises.

For example:

angular.module(`myApp`, []).config(function($urlRouterProvider, $stateProvider, $locationProvider) {
$locationProvider.html5Mode(true);
 $stateProvider.state('parties', {
url: '/parties',
 template: '<parties-list></parties-list>',
 resolve: {
 currentUser: ($q) => {
 var deferred = $q.defer();
 Meteor.autorun(function () {
 if (!Meteor.loggingIn()) {
 if (Meteor.user() == null) {
 deferred.reject('AUTH_REQUIRED');
 } else {
 deferred.resolve(Meteor.user());
 } }
});
 return deferred.promise;
 }
}
};
 $urlRouterProvider.otherwise("/parties");
});

If you still think it’s a good idea to create wrappers around those, please feel free to create a separate package for Angular Accounts, we would be happy to see those.

How it all looks together in a full component

angular.module('module').controller(function($scope) { $scope.search = ''; $scope.helpers({ parties() { return Parties.find({name : $scope.getReactively('search')})
} }); $scope.subscribe('myParties', () => [$scope.getReactively('search')]); $scope.autorun(() => { console.log('current search string is: ', $scope.getReactively('search')); }); });

Both the subscribe and the autorun will close automatically when the scope is destroyed.

Here is how it looks with the controllerAs syntax:

angular.module(`module`).controller(function($scope, $reactive) { // extend this variable with functionality of $reactive instead of $scope $reactive(this).attach($scope); this.search = '';
this.helpers({ parties() { return Parties.find({name:this.getReactively('search')}) } }); this.subscribe('myParties', () => [this.getReactively('search')]); this.autorun(() => { console.log(`current search string is: `, this.getReactively('search')); }); });

And with the Angular2Now syntax (Angular2Now is a way to write Angular 1.x apps in Angular 2.0 Syntax for better practice and easier migration):

let {Inject, Component, View} = angular2now;
angular.module('module');
@Component({selector: 'parties-list'})
@Inject(['$scope, $reactive'])
@View({templateUrl: 'parties-list.html'})
class partiesList {
 constructor($scope, $reactive) {
 $reactive(this).attach($scope);
 this.helpers({
 parties() { 
 return Parties.find({ name: this.getReactively('search') }) 
 },
 });
 this.subscribe('myParties', () => [ this.getReactively('search') ]);
 this.autorun(() => {
 console.log('Current search string is: ', this.getReactively('search'));
 });
 }
}

Example walkthrough:

$scope.helpers works just like Blaze Helpers.

It will make the parties properties equal to the array result of the find function.

The parties property on $scope will update as the cursor returned by find gets updated.

Also, the search property defined will become a reactive property on $scope when used explicitly with getReactively. whenever it changes the parties property will update to the new returned cursor by the anonymous function.

In the example above we are subscribing to the _myParties_ publication. We are doing it using the subscribe function on the scope, which would close the subscription when the $scope gets destroyed.

We send the second parameter to subscribe as an anonymous function that returns an array of parameters that will get sent to the publication. Any reactive functions called inside that anonymous function will cause the subscription to get updates with the new values (that anonymous function is automatically wrapped in a Meteor.autorun).

Also in that example we are using an example of $scope.autorun. The function inside the autorun will log the current search string every time it changes. That is achieved because we set up search on the $scope using getReactively, which makes the Angular search variable reactive everytime it changes.

Docs:

Technical considerations and design docs for this version can be found in this design doc.

Angular 2.0 Meteor:

In parallel, we keep updating and improving the Angular 2.0 Meteor package.

Both Angular 1.x and Blaze developers can start using Angular 2.0 Meteor right now.

My personal opinion is that it is the best and the cleanest integration with Meteor.

Example:

import {Parties} from 'collections/parties';
import {MeteorComponent} from 'angular2-meteor';
@Component({selector: 'parties-list'})
@View({templateUrl: 'parties-list.html'})
class partiesList extends MeteorComponent {
 parties: Mongo.Cursor<Party>;
 party: Party;
 constructor() {
 super();
 this.subscribe(`parties`, () => {
 this.parties = Parties.find();
 };
 this.subscribe(`parties`, partyId, () => {
 this.party = Parties.findOne(partyId);
 };
 }
}

Angular 2.0 Meteor Design doc (at the bottom)

Conclusion

As I’ve said at the beginning, this change is great for all Angular Meteor developers as it brings better performance. It has better support and access to resources as now you can use the same syntax and packages as the rest of the Meteor community.

Angular is now one of the official view layers of Meteor and the community and adoption are increasing extremely fast. This version puts Angular support where I always wanted it to be.

Looking forward we will continue to support and improve Angular 1.x Meteor and Angular 2.0 Meteor to keep Meteor as the best backend for Angular applications.

{"source":"medium","postId":"71bca6b80a6e","publishedDate":1449648000000,"url":"https://blog.meteor.com/angular-meteor-1-3-now-closer-to-the-standard-meteor-api-71bca6b80a6e"}