Our previous discussion talked about the intro to the subject, so today we're continuing.
The age of frameworks
At the same time, Google released AngularJS, a fully-fledged framework that included not only a scaffold for building an application, but also an accompanying set of testing tools. Initially, this framework won the hearts of developers and many popular websites were created with AngularJS, such as this McDonald's website:
Despite its popularity, many web developers became disappointed with AngularJS over time, but we'll examine why a bit later.
In 2011, Ember.js was brought to life as another SPA framework with a built-in template engine called Handlebars. The framework itself is built on the classic MVC (Model-View-Controller) architecture. Over the years, Ember has fostered a devoted community of developers who continue working on this tool. In 2020, they even released a definitive roadmap outlining goals for future growth. While you can still find articles with titles like: "Moving from React to Ember", it's still too early to dismiss Ember.js.
Backbone.js, AngularJS, Ember.js — more and more libraries and frameworks started to appear. Because of this, in the 2010s, web development was divided into back-end and front-end. It became more difficult to find a specialist with knowledge of server technologies, all the subtleties of JS, and all the necessary frameworks, since each of them had its own special features. Employers began to look for specialists not in terms of general web development knowledge, but for their ability to use a specific technology.
Diving into AngularJS
In 2010, Google released the AngularJS framework. The support of a large company (and good marketing) meant that AngularJS stood out amongst peers like Backbone and Ember.
Further, at that time, AngularJS offered an interesting approach to web development called "two-way data binding" which wasn't yet widely being used in the field.
Two-way data binding
AngularJS is a full-fledged framework created to simplify the SPA development process.
The main feature of this framework is that the view can change the model just as the model can change the view. It's useful for dealing with user actions. For example, the user enters data in the field → the model is updated → a request is sent to the server → a response is received from the server → the model is updated → the view is updated. This is how two-way data binding works.
AngularJS also introduced a design pattern unusual for client applications called Dependency Injection.
This mechanism allows us to easily connect the required dependencies to modules. Dependency injection can be pictured as follows: you have a team that consists of a developer (controller), a designer (service), and a content manager (a constant). They all work in the same office (DI container). When a developer needs a layout, they turn to the designer, who takes the content for the layout from the content manager. It's easy and quick because they're all in the same office. In the end, the developer gets everything they need to implement the project.
But AngularJS wouldn't be a framework if it just had a couple of patterns. Accordingly, it also includes many related tools:
- Animations — an animations module
- Karma — a test runner for unit-tests
- Jasmine — a framework for unit-tests
- Protractor — a framework for e2e tests
- Localization support
- REST-client — a module/library for server-client interaction (fetch, XHR analogue)
- Router — a library for working with the browser history. Simply put, this helps with navigating to different addresses within the application.
AngularJS has made a great impact on web development. AngularJS specialists working in the industry accelerated application development, rewrote applications, or wrote new ones with the help of this tool. But AngularJS wasn't a perfect framework by any means, and it has left many developers heartbroken.
AngularJS came out with a lighter version of jQuery referred to as jqLite. It was an option for apps that didn't need jQuery, or for developers who simply didn't like it. For modern apps, using jQuery is highly inadvisable.
Yet the main problem was something called the digest loop and its performance. The digest loop is what two-way data binding is based on. It works like this: when the model changes, all listeners are fired and the current values of the model are compared with the previous ones. If the listener function changes the model, this cycle will repeat until the checking process ends, that is, until the model stops changing. Even if the listener function hasn't changed, the loop will run the function at least once to make sure the model is consistent.
This is where the performance problems start as a result of the multiple loop runs and frequent model and view updates. Developers working with real-time data, or those whose apps contained large and frequently updated models, suffered from this the most.
This performance problem was never fixed because the creators would have to rewrite the core of the framework and quit using the backward compatibility. Therefore, Angular 2+, or simply Angular, was introduced later.
Revision: Angular 2
In 2014, developers in the dark labs of Google — not that far from Area 51, they say — started working on a new framework called Angular. Note that while AngularJS and Angular have very similar names, they represent quite different versions of this framework.
In September 2016, the first version of the final release was published. After that, they introduced Angular 4, skipping the third version entirely. This made the developers suspicious. Everyone was afraid of repeating the AngularJS problem caused by increasing the major versions and a lack of backward compatibility. Thankfully however, version 4 was fully compatible with version 2.
Angular has started releasing new versions with backward compatibility support, new functionality, and optimizations regularly. At the time of this writing, the latest release is Angular 11.
Angular's special features
In Angular, the component approach refers to the use and reuse of separate modules that contain the component's logic along with a view, markup, styles, events, and sometimes even a model. Moreover, each component is intended to solve a specific task, and is designed only to complete that task. For example, on a simple website, navigation might be a component. This component can then be transferred to another application simply by changing the configuration a bit. It's up to the developer to determine the size of the components in the project.
This is how a small Angular component looks:
In Angular, components can have their own state, but if you need a global state, you can use services to connect them via Dependency Injection. If the data flow is more complex and includes many transformations, as a rule, the NgRx library is used.
A few more new features of Angular compared to AngularJS:
- Cross-platform capabilities (PWA, Mobile, Desktop).
- Code-splitting, the splitting of code into small parts that are loaded on-demand as you work with the application.
- Significant increase in program performance.
Typescript is not only a feature, but also a disadvantage of Angular. It repels newbies because you can't write Angular applications without it. Some examples of Typescript problems for non-experts include: strong typing, complex types, access modifiers, generics, and errors that are sometimes very difficult to fix.
In addition, Angular is a complex framework that requires time to master — a lot of time, to be exact. Therefore, sometimes beginners give up and switch to "lighter" frameworks. But those who are patient and stick with learning Angular are rarely disappointed.
React — Facebook's silver bullet
In 2011, the Facebook team experimented with creating frameworks that would solve their performance problems. The application was rapidly adding features, and developers were drowning and unable to keep up with the cascading updates. The frameworks that existed at the time like AngularJS, Backbone, Ember, Knockout weren't able to provide a suitable result.
Facebook developers came up with a prototype solution called FaxJS. This project had its own features:
- A component approach
- Reactivity — the view changed according to the change in the state of the component
- Fast rendering
Initially, Facebook Ads was built on FaxJS, then Instagram tested it on its feed. The experimental FaxJS project gave rise to the React library of today. By 2013, React had already been introduced as an open-source library at the JSConf US.
The virtual DOM is a lightweight copy of the HTML document's real DOM. It takes the form of a JS object in the application's memory. It aggregates all dynamic changes, then applies them to the real DOM.
The main problem with the native DOM is its speed when working with dynamic data. It requires a lot of resources to read and write data. The virtual DOM is not just a simple object that stores data, it also has efficient algorithms for comparing and grouping changes to the individual parts of the HTML DOM.
A small section of the DOM tree, represented as HTML:
The corresponding segment inside the virtual DOM:
The virtual DOM is able to "react" to any UI changes in a much faster and more efficient way than the HTML DOM itself.
Here's some JSX markup:
And this is the result after converting the JSX above into JS:
We've already touched on the MVC and MVVM architectural patterns. React and its components are the letter “V” in MVC / MVVM, that is, React represents the "view" portion of the pattern. With React, we can write reusable components and assemble large functional blocks by using smaller components.
Using JSX, this is how you can describe a form that consists of different components:
With this component-based approach and composition, we can make views of any complexity.
React is a view layer that doesn't have any models. In other words, there's no place to store data. Moreover, data practically can't be reused between components, because the transfer of properties from one component to another at several levels leads to the anti-pattern or prop drilling.
For example, you might need to pass properties from a container component, down into a navigation component, into a sub-menu component, and so on. In extreme examples of prop-drilling, there might be a large number of levels where properties are being passed down and this can result in management issues and plenty of bugs along the way.
The problem with this behavior is that when you change this data, or add or remove a property, you need to go through all the components where the transfer takes place and make the updates.
The Facebook developers understood this problem, so they added a new tool to the React ecosystem, referred to as Flux.