Table of contents
- 1. When would you use the constructor() versus the ngOnInit() method?
- 2. What are some performance techniques we can apply to Angular applications?
- Ways to improve Network Performance
- Techniques to improve Runtime Performance
- 3. What inspiration from Web Components does Angular take and implement?
- 4. Why is Ahead-of-Time Compilation a good thing?
- 5. What practices could you adopt to share data to another component elsewhere in the component tree?
- 1. Sharing data between Parent <-> Child component
- 2. Sharing data between unrelated components
- 6. Why adopt a modular architecture that can be lazy-loaded?
- 7. Why do Observables and reactive programming fit well with Angular?
- 8. Why would you adopt Reactive Forms over Template-Driven Forms?
- 9. What benefits does using content projection bring to component composition?
- 10. What benefits does uni-directional data flow bring to component architecture?
1. When would you use the constructor() versus the ngOnInit() method?
To answer this question, we need to understand a component lifecycle and the role of constructor
. Angular creates components based on two phases: constructing components tree and running change detection.
The constructor()
method is invoked in the first step. Component Lifecycle Hooks are methods on Component
or Directive
that Angular calls at a specific moment of the change detection process.
The ngOnInit()
method is the second at this lifecycle sequence. It is called once and means that the object is ready to use because Angular already set all input properties and displayed the data-bound properties.
The code added to the constructor
is always initialized before the ngOnInit() method. We need to be sure that logic set in the constructor
is not added too early (when the component is out of control).
We typically use the constructor
to Inject Dependencies. Also, practical experience says – the less logic in the constructor
the better. Moreover, remember that input binding is not available in constructor
considering the change detection and the Input
communication mechanism.
The ngOnInit
is a great place to add logic for a component that is outside of Dependency Injection
, Input Binding
, DOM
, Router
ecosystem.
2. What are some performance techniques we can apply to Angular applications?
One of the most important requirements for modern web applications is to provide smooth User Experience. Angular allows you to create a complex app but is crucial for developers to take care of the app performance from the beginning.
Before we start the analysis of practical cases let’s take a look for a recipe to boost the performance of any app: Minimize, optimize, remove unused and repeatable code. And remember – the less code you use to implement your app, the better!
If we’re talking about the speed of web applications we should consider two aspects:
- Network Performance – methods for latency and bandwidth reduction to improve the app’s load time,
- Runtime Performance – techniques to optimize rendering and improve change detection performance.
Ways to improve Network Performance
To improve Network Performance and optimize the load time of Angular app we need to take care of bundle size. The Ahead-of-Time compilation is one method you can use to get it. Although, it can also improve Runtime Performance by reducing the number of computations required for app’s rendering and performing the compilation as part of the build process.
Furthermore, let’s focus on reducing the unused code. We should remove template whitespace, use code splitting techniques, minify and eliminate dead code (there are useful tools that can be helpful like uglify, google closure copier, and so on). Also performing tree-shaking can be very useful as a way to avoid exporting the unused code.
Similarly, the other solutions that impact an app’s speed:
- pre-fetching or caching assets (images, styles, modules or data),
- lazy-loading,
- app shell model,
- Service Worker.
Techniques to improve Runtime Performance
Runtime Performance in Angular is strictly dependent on the Change Detection
process. Angular performs the mechanism of Change Detection over the entire component tree. In the case of complex apps, it can be a heavy computation. To improve the performance we try to disable the Change Detection where it is unnecessary and don’t run it for subtrees which are not supposed to to be changed based on the recent actions. Also, we perform the mechanism only when the component has received different input.
Let’s take a look at practical examples of how to optimize Change Detection Performance:
- using onPush strategy,
- running Change Detection outside Angular (outside of zone.js),
- detach and reattached custom Change Detection.
It’s time to consider how we can boost Angular performance by simple changes in component templates. A common mistake impacts on the speed of the app is using functions in interpolations instead of pipes. Follow the example and avoid this bad practice:
// Bad practise:
{{ methodTransformation('some_value') }}
// Good practise:
{{ 'some_value' | pipeTransformation }}
Furthermore, remember that rendering the DOM elements is the most expensive operation when adding elements in the User Interface. *ngFor
directive is very useful for rendering a collection but we should consider to minimize the number of DOM elements and reduce time of loading. We can apply virtual scrolling and use ng-container
to deal with performance issues for a big amount of DOM elements.
Despite, there is a good option to use trackBy
function inside ngFor directive. This parameter helps Angular to identify object uniqueness so there’s no need to remove all the DOM elements associated with the data and create them again. The trackBy
function takes two parameters (index and the current item) to return the unique identifier. You can see below, how to use it in your app:
<li *ngFor="let item of list; trackBy:identify">
{{ item.id }}
</li>
3. What inspiration from Web Components does Angular take and implement?
Web Components in Angular are called custom elements
. They are powerful and indeed when you want to write code in a framework-agnostic way. Although, there are more advantages of using this feature. It allows you to improve reusability and readability of your app. And make it more consistence and maintainable. Moreover, custom elements in Angular are a great way to add components to an app at runtime.
Web Components map Angular functionality to native HTML elements. That makes them universal to read by any browser that supports custom element Web Platform feature (through polyfills).
How does it work? We use JavaScript code to create and control the element’s content by extending HTML Element and defining a tag. As a result, we get components with looks and behaves like any other HTML elements.
Take a look for the inheritance of Angular Custom Elements:
CustomElement —(extends)—> NgElement —(extends)—> HTMLElement
As you can see above, to create a custom element in Angular we need to extend the NgElement interface. NgElement inherits behaves of HTMLElement by its interface enhancement. The @angular/elements
package is crucial in Angular custom element implementation. You can simply add it to your app using the only one command in CLI:
ng add @angular/elements
This package exports CreateCustomElement() API. It’s a basic interface to create cross-framework components by attaching DOM API functionality of Angular’s components and change detection features.
We can transform Angular components for elements understandable by browsers but we still provide all infrastructure specific for Angular. It’s great how the mapping works – you get automatically connected: view, change detection system and data binding process of the defined component. Custom elements bootstrap themselves with automated lifecycle: when you add it to DOM they start automatically, in turn, are automatically destroyed when you remove them from DOM.
Here you can see an example of how to convert a regular Angular component to a custom element:
import { Component, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { MenuComponent } from './menu.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
constructor(injector: Injector) {
// Convert `PopupComponent` to a custom element.
const MenuElement = createCustomElement(MenuComponent, {injector});
// Register the custom element with the browser.
customElements.define('menu-element', MenuElement);
}
}
4. Why is Ahead-of-Time Compilation a good thing?
AoT (Ahead-of-Time) Compilation is a way to compile an Angular app at build time. It allows a browser to understand the templates and components provided by Angular. The compiler converts Typescript code and Angular elements into efficient JavaScript code during the build phase. It happens before the browser downloads the full client version and loads the code. This process is crucial in the context of app performance. Firstly, regarding the load time of Angular app – AoT compilation brings efficiently tree-shake during bundling and is able to remove all unused directives and also eliminate separated asynchronous requests. We can simply reduce application payloads because we get a smaller bundle size and we don’t need need to download the Angular compiler if the app has been already compiled.
Although, the compilation can also improve runtime performance. This is a great way to reduce the number of computations required for the app rendering cause the compilation is performed as part of the building process.
From other perspectives, Ahead-Of-Time Compilation is a good thing cause of security and error validation. The app is compiled before we serve it to the client. We can detect errors before we show it to the users. AoT compilation can also eliminate opportunities for injection attacks.
To use AoT Compilation for your app, you can simply run commands in CLI with the flag --aot
, for instance:
ng build --aot
ng serve --aot
Alternatively, you can build your app in production mode. This option uses AoT Compilation by default:
ng build --prod
5. What practices could you adopt to share data to another component elsewhere in the component tree?
To answer this question we need to consider how components are located in the component tree. If we want to share data to another directly related component, we can use the decorators: @Input()
, @Output()
. However, the bigger distance between components in the component tree (means components less related), the more complex communication using only Child/Parent connectors.
Services provide an alternative method of sharing data between components (also not related). In shared service, the interface enables bi-directional communication. The service instance limits the scope of data access and disables getting data for components outside this component subtree. To realize communication and sharing data between components we can use BehaviourSubject()
or Subject()
from RxJS library.
From the other perspective, we can adapt Redux pattern and store the tree state in the ngrx store and then pass it to the components via selectors. In this solution, we keep separated app data from components. We don’t have a direct interaction between components in sharing data process. The communication is possible via the store. A great advantage of this approach is the fact that the distance between components in the component tree doesn’t matter.
Take a look for a cheat sheet on component interaction:
1. Sharing data between Parent <-> Child component
- Input binding
- Input property setter
- EventEmmiter()
- template reference variable (local variable)
- ViewChild injection
2. Sharing data between unrelated components
- Subject() or BehaviourSubject() in a shared service,
- passing data to components via NGRX selectors
6. Why adopt a modular architecture that can be lazy-loaded?
Lazy-loading is a design pattern that doesn’t load all ngModules that build Angular app but only indeed modules for a specific route. It’s a great way to decrease bundle size and reduce the loading time of your app. The more complex app, the more relevant usage of this pattern.
How to implement this? We can define lazy-loading modules as a part of Route Configurations
. Version 8 of Angular provided a new possibility to use Dynamic Imports. Let’s take a look at this example:
{ path: '/admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }
In this case, for the path ‘/admin’ Angular lazy loads part of the app – AdminModule.
Lazy-loaded modules are a good practice to improve app performance but consider declaring the default page route as non-lazy. Don’t apply it for default route because you can get the reverse effect. It could slow down the initial page rendering by triggering extra HTTP requests and performing unnecessary computations during the initial page load.
7. Why do Observables and reactive programming fit well with Angular?
Observables and reactive programming are extremely effective to deal with asynchronous programming. Some elements of Angular architecture return streams of values by default. Let’s take a look for sources of asynchronous streams in Angular that return Observable:
- http methods (get(), put(), …)
- async pipe
- Router.events
- EventEmmiter
- AbstractControl.valueChanges
- ActivatedRoute.params
Observables are pretty productive to handle values emitted asynchronously. A bunch of operators of reactive programming allows you to transform, filter data and make your code clear and easy to understand. RxJS library offers a lot of useful methods that can simplify data operations in your app. Aside from asynchronous programming, observables fit well for event handling and dealing with multiple values.
This technique is more powerful than other solutions in JavaScript e.g. Promises. Observables can deliver a few values of any type, in a synchronous or asynchronous way, are possible to cancel and allows you to handle error easier. Furthermore, observables are lazy by nature the request is only made when we call subscribe
.
8. Why would you adopt Reactive Forms over Template-Driven Forms?
Template-Driven Forms
are a simple and quick way to apply form elements in a component template. However, for more complex cases this solution can be inefficient. Reactive Forms, at first glance, might be longer to implement but finally, they better deal with Form Validation, storing logic in one place and grouping fields in collections.
From the other perspective, you can easily create unit tests for Reactive Forms and make your app more readable. Despite, the main point is that the form controls and the form itself provide an Observable API. We have a bunch of useful operators that allow you to work on streams of value emitted inside a form. In conclusion, It gives you better control of form.
9. What benefits does using content projection bring to component composition?
Content projection
in Angular is a way to inject dynamic content at a specific point inside a Component Template. The main concept is to project content from Parent to Child Component as a placeholder. To use it, we need to apply <ng-content>
tag inside a Parent Template. After rendering, we will see the injected element inside DOM output.
This technique is an efficent method to build reusable components that have the same behaviour for different content. Through component projection, we can simply reduce complexity in component composition, as well as make the code more readable and clear.
10. What benefits does uni-directional data flow bring to component architecture?
The uni-directional data flow
is a basic concept for developers to understand: how do components interact and share data inside the component tree and how does change detection work in this process? In Angular, the direction of data flow is the following: from parent to child component. The Change Detection
is propagated from the root towards the leaf nodes. The cycle is completed only if all leaf components are updated.
What are the benefits of uni-directional data flow in Angular? Firstly, it brings an efficient change detection process. Despite, it makes the data flow predictable, and also prevents cycles (loops) in the Change Detection
mechanism.