Not too long ago Angular 2 hit "Final Release" which means a lot more people will be trying it in the coming months. Moving from Angular 1 to Angular 2 introduces some new concepts, patterns, and tools for building your front end applications. The changes will take a bit of adjusting to adapt to, but the good news is you don't need to press delete on your existing Angular code base to use these patterns.
In this post we're going to focus on the implementation into Angular 1 of these patterns the goal being twofold, one - to familiarize ourselves with these patterns and the value they provide; and two - demonstrate that you can implement these patterns in Angular 1 and in the process make the transition to Angular 2 that much easier.
So what are components?
Why use components?
- separation of concerns
- loosely coupled
One or two of these characteristics would definitely make a pattern or tool worth looking into, but getting all of these makes a compelling argument to use components in your architecture. Its easy to see how you can build a framework where large parts or maybe even whole applications can be composed by combining "base" components into to bigger "feature" level components.
What are Child Views?
Child Views are the "detail" in the Master-Detail view pattern. It would be the detail view you would navigate to if selecting something from a list. The concept is so ubiquitous to web development that you've probably used the pattern without even noticing. Its relevance in this discussion is that Angular 1 had no native support for child views and often required workarounds to implement, but in Angular 2 the concept is baked into the framework
Why use Child Views?
- separation of concerns
One Way Data Flow
What is One Way Data Flow?
One way data flow is a pattern that has emerged as a byproduct of dealing with the issues of poor performance and state management with two way binding. The concept is simple, all data or "state" in your application flows from one source - to change data - changes are applied to the source and the source then updates all data consumers in the application.
Why use One Way Data Flow?
- traceable state
- better performance
- easier debugging
- decreased code complexity
Once you apply this "one source" approach, as part of your One Way Data flow pattern, tracking changes in your application becomes easy. Once you start logging state changes a lot of possibilities open up - - when debugging -- easily review the changes in state at the time the error occurred; -- need to "undo" -- easily identify each change that needs to rollback; -- need test data -- re-use the log to identify the exact data/state you need to run your test. Let's also not forget it makes you code easier to understand because all changes run thru one central location. In comparison to two-way binding in Angular 1, we know changes to state can come from multiple directions, users updating a field, variables bound to other variables - it can be hard to understand what's going on in the app. We would also be remiss if we didn't mention the performance cost tied to watchers and digest cycles.
What are Events?
Events are another one of those concepts that we are all familiar with and have worked with in one form of another. In this context we're talking about the expanded role their playing in a lot of current front end development and the different patterns being used to leverage them to greater effectiveness. The increased popularity of paradigms like "Reactive Programming" and frameworks like Rxjs are causing events to be looked at in a different light. In Angular 2 this is evidenced by the "embedded" use of Rxjs in its architecture and doing things like using "Input" and "Output" events for Component communication.
Why use Events?
- loose coupling
- separation of concerns
When we say "use" Events we're talking about using them to create leaner architectures. The use of an event pipeline in your architecture, lets your code just plug into the pipeline as needed and is freed of any requirement to be aware of, or directly interact with, the producer of the event. With regard to Angular, using something like a "Publish/Subscribe" event pattern greatly streamlines your code. Think about two-way binding in Angular, specifically think about one of the classic examples where input text is bound to a label or header. Everything plays nice when working from the same controller and view, what if we needed to have two-way binding across controllers or components - say bind to a field in the header or footer - think about the code you would need to write to wire these two pieces together - not so easy to do or maintain - leveraging a pattern like Publish/Subscribe provides a clean maintainable implementation.
Its important to note that we also rely on a couple of tools that play a pretty standard role in Angular 2 development, Typescript and Webpack. We'll give you a quick overview, but definitely get to know them better as they have a lot to offer
Now that we are familiar with the concepts and some helpful tools, lets dive into the application and understand how we use them in Angular 1.
I've gone ahead and created two demo apps that incorporate these practices and tools. The app is the "At the Movies" dvd store, to allow for comparison, we have an Angular 1 version and an Angular 2 version of the app, . Click on the link to download and experiment with the apps at your leisure, but its functionality is pretty simple and does the following
- users can select a movie
- users can read reviews
- users can add a review
- users can buy a dvd
- the app tracks the best-selling movie
- the app tracks the best-reviewed movie
Below is a diagram of the of the architecture for reference, while reading or looking at the code.
Let's now look at how we implement each of these patterns
- Angular 1 - angular directives + Webpack
- Angular 2 - native angular 2
I know what you're thinking, doesn't the latest versions of Angular 1 have a "Component" directive?
Current versions of Angular 1 do have a "Component" directive which I purposely decided not to use as I wanted to demonstrate with even older versions of angular it's still possible to create components.
Now lets look at our "movie-list" folder which has all the ingredients for a component
A couple of things to point out
- we are using Typescript classes which at compile time will result in a module, wrapping our code in Typescript classes and having it output as a module, allows for the "class" to be used as a dependency in other parts of our code via the "require" or "import" statement.
With this setup, assuming you using this pattern in other applications, you can copy your movie-list folder into another application and just "require" it or you can configure Webpack to bundle it into an output file that can be included on an html page. Now, just to leave you hungry for more, think about the other stuff Webpack can bundle into this component for you, like Angular Services or you Jasmine unit tests...hmmmmmm...
- Angular 1 - ui-router
- Angular 2 - native angular 2
One of the best ways to implement child views in Angular 1 is with ui-router. The ui-router solution is pretty popular and considered a best practice by many with regard to routing - here is a great article spelling out its merits
In our app we use child views for our main page content and in our movie page to pop in reviews, input form view for adding a review, and a purchase view for users to buy a movie.
the implementation revolves around three steps
- a place for views to land, which is
<ui-view>, this is the angular equivalent of ng-view. Here we see the examples from the app and movie HTML.
- routing to views, done by using
ui-srefinstead of href.
- last but not least is the configuration of the router. One thing we should point out, as you can see below, is that our route configurations are setup to route to components; and child views are really just "child" components. We can see an example of child views/components in the configuration of the "review", "reviews", and "purchase" routes as they configured with a "parent" parameter which is the "movie" route. As you can see, creating application features as components is a pattern that fits well with routing, so much so that this approach is exactly how Angular 2 has designed its routing to work.
One Way Data Flow
- Angular 1 - ngRedux
- Angular 2 - ng2Redux
One Way Data Flow is implemented with the use of Redux and Redux is a tool the comes to us from the React js community. Redux has quickly established itself as "the" way to implement the Flux pattern, which is the prescribed pattern for working with React js.
There are no real differences in the implementation between Angular 1 and 2, there is an "angular" Redux version for both. The concept behind redux is relatively simple
- some event generated by the application requires action on the application state
- the designated code that will perform the action will take a copy of existing state, apply the action to the copy and return the copy as the new state of the application.
There is more to the pattern, and requirements around using pure functions and maintaining immutability with regard to application state --- follow up on the links above for details.
In our case we use Redux to implement this "one source of data" requirement of the One Way Data Flow pattern. Redux has all the tooling to implement the pattern and it is what helps our movie information stay in sync, the implementation of Redux revolves around three steps
- actions - which are objects that have two properties a "type" and a "payload". The "type" is a description of the event that has just taken place and the "payload" is whatever data is required to implement the required action triggered by the event.
- reducers - which take application state and an "action" and execute the "action" logic required to transform state
- an application store which manages our data and uses three methods to work with the application
- dispatch method - which takes an action and applies the reducer code
- getState method - which returns state
- subscribe method - which allows application code to get notified when application state changes
- Angular 1 - Rxjs
- Angular 2 - Rxjs
As hinted at above we leverage the "Publish/Subscribe" Event pattern to keep our code decoupled- to do this we use Rxjs. Like Redux the implementation is the same for both versions, but unlike Redux you can use the same Rxjs code in both, there is no need for Angular 1/2 differentiation.
Rxjs is a pretty big tool set that comes with a lot of utilities for processing streams of event data. The use case we've implemented uses something known as a "Subject" which allows us to "publish" and "subscribe" to events, we've wrapped our Subject in a Angular service to provide some encapsulation and control over its use. Let's review the implementation
- Potential publishers would use the service to publish information to the pipeline, in our case we broadcast to the system that a new movie review has just been created, shown below
- As you see in the code below, potential subscribers use the service to register a handler that gets notified of the event and applies the corresponding logic.
As you see, implementing this pattern with Rxjs is very lightweight, other approaches could've been used to apply the "Publish/Subscribe" event pattern but I'd be hard pressed to find one that does it so cleanly. The other upside is that the Rxjs tooling allows for lots of options with regard to manipulating the subscribed stream of data, think in terms of an object(s) being passed thru the pipeline and the ability to apply collection logic like "map" or "filter" on them. This is exactly the kind of thing Angular 2 has done by wrapping the output of its "http" service as a subscribe-able data stream via Rxjs.
In summary we've talked about some significant patterns and have implemented them in Angular 1, fair to say that each of these topics could be a post unto themselves and definitely demand further exploration. I've purposely kept things light and tried to hone in on the main idea or principles behind some of these patterns, to serve as a starting point. Hopefully it's sparked a bit of inspiration and curiosity and started the wheels turning on how you can improve your existing code base via one of these topics.