One of the reasons for Angular’s success is the flexibility in the building blocks the framework provides. There is a delicate balance between productivity and flexibility. A heavily opinionated framework can be make a team highly productive, but the opinions can also make it harder to mix in additional features that conflict with the opinions, or sit outside the vision for the framework. Angular strikes a nice balance.
When I first thought about mixing PhysicsJS into an AngularJS application, I couldn’t see how a library based on timer ticks, frame rates, and a canvas could work with a framework like Angular, with its digest cycle and directives.
But, after 30 minutes I discovered that not only could these two work together, but Angular was flexible enough to provide some nice enhancements to the PhysicsJS experience.
Go to PhysicsJS + AngularJS to see the following code in action. The code is based on the tutorial “Creating a scene of colliding polygons”.
The first order of business is wrapping PhysicsJS components with services. Physics is the primary export of PhysicsJS, and this object provides factory methods to create shapes and behaviors. We can inject Physics into other components of the application that need to create shapes, but we’ll also create some additional services that use Physics to setup the “game loop” ticker and the world that contains all the shapes and behaviors for a scene.
var app = angular.module("app", []); app.value("Physics", Physics); app.factory("world", function (Physics) { var world = Physics(); world.on("step", function () { world.render(); }); return world; }); app.factory("ticker", function (Physics, world) { var start = function () { Physics.util.ticker.on(function (time) { world.step(time); }); Physics.util.ticker.start() }; return { start: start }; });
Directives can transform a PhysicsJS scene from imperative code and into a declarative language. Here are a few simple directive definitions.
app.directive("physicsBehavior", function (Physics, world) { return { restrict: "E", scope: { name: "@" }, link: function (scope) { world.add(Physics.behavior(scope.name)); } }; }); app.directive("physicsBody", function (Physics, world) { return { restrict: "E", scope: { options: "=", body: "=", type: "@" }, link: function (scope) { scope.body = Physics.body(scope.type, scope.options); world.add(scope.body); } }; });
These types of directives will let us write markup like the following.
<physics-canvas width="500" height="500"> <physics-body type="rectangle" body="main.box1" options="{x:450, y:10, vy:-0.3, width:5, height:5}"> </physics-body> <physics-body type="rectangle" body="main.box2" options="{x:250, y:210, vy:-0.3, width:5, height:5}"> </physics-body> <physics-edge-detection min-x="0" min-y="0" max-x="500" max-y="500" restitution="0.3"> </physics-edge-detection> <physics-behavior name="constant-acceleration"></physics-behavior> <physics-behavior name="body-impulse-response"></physics-behavior> <physics-behavior name="body-collision-detection"></physics-behavior> <physics-behavior name="sweep-prune"></physics-behavior> </physics-canvas>
Controllers can build models that interact with objects in the PhysicsJS world. The API allows us to apply forces and change the geometry of objects. One difficulty I did have is seeing the updated appearance of a shape after changing attributes. The code needs to set the view property of a shape to null for changes to attributes like width and height to appear. PhysicsJS will recreate the view on the next tick.
model.kick = function () { model.box1.applyForce({x: 0.1, y: -0.2}); model.box2.applyForce({x: -0.1, y: -0.2}); }; model.grow = function () { model.box1.geometry.width *= 1.5; model.box1.geometry.height *= 1.5; model.box1.mass *= 1.5; model.box1.view = null; model.box1.recalc(); };
Anyone who has worked with jQuery widgets or native events knows that Angular needs to know when model data changes in order to update the DOM with new model values. Generally we accomplish this goal by wrapping code inside a scope’s $apply method.
The interesting thing about working with PhysicsJS is that the physics engine is constantly updating a canvas based on a timer, so it is not necessary for Angular to know about changes in shape properties, like position and acceleration. It just works.
Although there would be a lot more work needed to make a complete and general purpose wrapper for PhysicsJS, the integration with an Angular app works in a straightforward manner.