Components are the building blocks of an Angular application. Components bring reusability to a view. Imagine a view that presents user profiles and order information in the sample application that we are building (a superhero profile). We might need to present the same data in many places. Instead of duplicating the code in each instance, let’s create a component and reuse it.
This chapter introduces Angular components. It covers creating new components. An Angular application needs metadata that describes a component. Metadata defines how we use the component in the application. This chapter elaborates the metadata elements, which are typically coded as arguments to the TypeScript decorators on top of the component class.
This chapter also explains how to provide input data to a component and how to receive output from a component. The chapter concludes by discussing lifecycle hooks for a component.
Introduction
DOM Tree
Figure 4-1 is a visual representation.
Angular HTML Template with a Component Among Elements
Create a Component
Angular CLI Command for Creating a New Component
Note
The path specified, superheroes-material-design/<component-name>, creates the component under the superheroes-material-design folder. It also looks for a module with the name SuperheroesMaterialDesign.module. Without this path reference, the component is created in the root module.
Angular Module with the New Component Declaration
A TypeScript class file component
A template HTML file
A style sheet/CSS file
A unit test file
A Component Class in TypeScript
The following are the metadata attributes frequently used with components.
selector
We refer to a component using the selector value. In this example, we provided a value: superhero-profile. To use this component in another component in the application, the template (HTML file) would create a <superhero-profile></superhero-profile> element (see Listing 4-5).
Note
This attribute is inherited from another decorator, Directive. Components are a type of directive. Directives are discussed later.
templateUrl
A template defines the view of a component. It is the HTML intermixed with Angular bindings and syntaxes. templateUrl refers to the component’s HTML template. Angular CLI places the template in the same directory as the TypeScript file component.
HTML Template for Sample Superhero Profile
template
Typically, the HTML template code is separated in another file; however, if you prefer to keep the template and the TypeScript code in the same file, use the template attribute (instead of templateUrl). It might be a good idea to use the template attribute for a small number of lines of code (perhaps four to six lines of template code); otherwise, it could be convoluted to use template and the TypeScript in the same file.
styleUrls
Style sheets, or CSS, manage a component’s look and feel. It takes care of colors, fonts, sizes, the margin width and height, and so forth. Styles are preferably separated into one or more files. Specify the relative path of the styles here.
style
The style attribute is used for inline styles; it is in the same file as the TypeScript component.
encapsulation
Traditionally, it is difficult to scope CSS in a web page. Over time, an application’s complexity increases, and a lot of presentation and styling code accumulates. Web pages import styles at the HTML page level. Considering there is a single HTML page that loads at the beginning of an SPA (single-page application), it might mean the style sheet file is used by the entire application. With multiple developers on any typical team, it is very easy to step on one another’s toes. Changing the style in one functionality could adversely affect rest of the application.
emulated: The default option. Angular ensures that the CSS used in a component does not interfere with other components. This is done by adding a surrogate ID to the host element. This host element is added to all CSS selectors that use the styles and elements.
shadowDom: A new web standard popularized by Web Components. It helps scope the styles for natively supported components in a web page. This option works well for all browsers that support Web Components and the standard.
none: Styles are global.
providers
The providers attribute accepts an array of tokens. With DI (dependency injection) in Angular, the injector uses provider to create an instance and supply the object to the decorated class.
Let’s simplify this explanation. The decorated class is the component. It is a TypeScript class. Components in Angular typically use a reusable class instance (Angular services). It needs to be “provided”—that is, provide metadata for the injector to create an instance. Once the injector creates an instance, it is readily used by the component.
Note that “providers” is a field in the ngModule decorator mentioned in the Chapter 3. An Angular service (reusable TypeScript class) can be provided at the root, module, or component level.
A TypeScript class can maintain the state. The state is a class level variable or an object with data. If we provide at the root level, the state is shared across the application. It acts as a singleton for the application. If we provide it at the module level, the state is shared within the module, which means that it will provide a single instance throughout the module.
If it is provided at the component level, it acts like a local instance and does not share the value with any components; however, the instance is shared in the child components.
Note
Angular 1.x has services and factories. The state of a service or a factory is maintained across the application. The object and data is shared among controllers (in Angular 1.x). Angular 2+ depends on the provider. By default, the TypeScript classes that act as services are transient. Providers define how data is shared in the application.
Services providing dependency injection is discussed more in Chapter 7.
viewProviders
Projected Content
This is useful for allowing the contents of a component to be supplied “on the fly.” A component can apply its functionality on any content projected between the opening and closing elements. A component library might extensively use content projection. The downside, however, is that the author of the component does not have control over which content is projected. It is coded by the component’s consumers.
If you need to restrict a provided class to be used by only the real child components and not by the projected content, you may use viewProviders.
Note
Use <ng-content> (a built-in directive in Angular) for projecting content.
An Example to Showcase Projecting Content in Angular
superhero-profile-footer in SuperheroProfile Component Template
SuperheroProfile Template with Support to Projected Content
SuperheroProfile with Projected Content
In this example, both approaches result in the same view because we are using the same child component and projected content: superhero-profile-footer. The “A superhero made in Hollywood!” text is from the footer (see Figure 4-4); however, the later approach (content projection) allows any component or DOM element to be used as the footer; it does not need to be superheroProfileFooter. The footer is supplied to the component externally.
Note
For simplicity, this section does not describe the features of <mat-card>. Positioning content at the bottom of the card makes it look like a footer; however, in a future chapter, I discuss a better way to use a Material Design card.
Using viewProvider
A Reusable Class Providing Contents for the Superhero Components
SuperheroText Provided at Parent Component
A Child Component Injecting a Class Provided by Parent
Direct Child Component Using superheroText Works Well
Input to a Component
Components are building blocks of an Angular application. They are reusable blocks of code. A component is expected to be designed with a single responsibility principle in mind. A component should be responsible for a particular view in the application.
So far, we have built a component to show a superhero profile. The same component could be used for any superhero in the system. This means, it expects input. The component processes and presents the data in a uniform manner.
To accept input into a component, Angular follows a natural HTML way: element attributes. As you might have noticed, a div tag can accept additional input, like a CSS class, with a class attribute. It accepts a unique identifier with an id attribute. Similarly, a text field accepts text with a value attribute, and so on.
While building custom elements with Angular, there could be one or more attributes added. The TypeScript code has a reference to the variable, and you can also use it in a template for presentation.
Component Class with Input Elements
Notice the input to the decorator in the livesIn field. It allows you to rename the component element attribute. In this case, we can use livesIn as lives-in in the template.
Input in the Template
Validate Input with a Setter
Use a Setter to Validate Input
Validate Input with the ngOnChanges Lifecycle Hook
Setters are effective for validating input when the number of input attributes is small. The downside to this approach is that we need a separate function for each attribute. We can combine all validations to a single function with ngOnChanges.
To use ngOnChanges, import and implement the onChanges interface. Override the ngOnChanges function (see Listing 4-18). Notice the SimpleChanges parameter. The object will have the previous value and a new value in each field.
ngOnChanges Override
Output from a Component
A component can emit events, which can be tapped into by a consuming component. To demonstrate an output event, consider enhancing the Superhero Profile component. Let’s add a button that emits the complete superhero object to the consuming component.
AppComponent Accessing the flyWithSuperhero New Event
A Type Representing Output
Create an Event Emitter and Decorate It
Emit the on click Event of the Button in the Component
Lifecycle Hooks
A component’s lifecycle is managed by the framework (Angular). It creates an instance of the component, updates data as required, and finally, destroys the component. The framework provides the ability to tap into various moments in the lifecycle and perform additional actions. These functions, which are called at various stages in the lifecycle, are known as hooks, or lifecycle hooks.
ngOnInit(): Invoked as the component is initialized. It is in addition to constructor. The constructor is meant for simple initializations, such as setting a default value on class variables. Use ngOnInit for more complex tasks, such as invoking a remote API to obtain data for the component. To define the ngOnInit() lifecycle hook, implement the OnInit interface, which is part of @angular/core (see Listing 4-22).
ngOnDestory(): Invoked as the component instance is destroyed. Perform cleanup actions in this function. To define the ngOnDestory() lifecycle hook, implement the OnDestroy interface, which is part of @angular/core (see Listing 4-22).
ngOnChanges(): Invoked as and when the values of the input attributes change. This provides control for the developer to perform additional actions if the input values meet certain criteria.
Exercise caution when using this lifecycle hook. The number of times that this lifecycle hook is invoked is high. In a typical component, data-bound input attributes change often. If an additional action needs to be performed every time it happens, this could affect the application’s performance.
To define the ngOnChanges() lifecycle hook, implement the OnChanges interface, which is part of @angular/core.
ngDoCheck(): Not all changes to the input attributes are caught by the framework. Use ngDoCheck() for a manual check on all input attributes (or the variables of interest)on the component.
Exercise caution while using this lifecycle hook. This hook is invoked every time a change occurs in the application. Hence, using this lifecycle has a high cost to the application.
ngAfterViewInit() and ngAfterViewChecked(): The Angular application is a tree of parent/child components (or views). The ngAfterViewInit()lifecycle hook is called after the child view of the given component is initialized.
The ngAfterViewChecked() lifecycle hook is called after data has been changed (updated) for the child views or components. This lifecycle hook is invoked in the parent component. Child components may use their own ngOnChanges() or ngDoCheck().
To define the ngAfterViewInit() or ngAfterViewChecked() lifecycle hooks, implement the AfterViewInit and AfterViewChecked interfaces, respectively. The interfaces are part of @angular/core.
ngAfterContentInit() and ngAfterContentChecked(): These lifecycle hooks are in projected content, which is the markup provided between the component’s opening and closing tags. Refer to the “An example to showcase projecting content in Angular” section for more information on projected content. The ngAfterContentInit() lifecycle hook is called after the projected content is initialized. The ngAfterContentChecked() lifecycle hook is called after the projected content data has been changed (updated).
To define the ngAfterContentInit() or ngAfterContentChecked() lifecycle hooks, implement the AfterContentInit and AfterContentChecked interfaces, respectively. The interfaces are part of @angular/core.
ngOnInit() and ngOnDestory
Note
Implementing an interface for a lifecycle hook is a good practice; however, it is not mandatory. We may define the lifecycle hook function without the interface. It will still be invoked by the framework. If you make a mistake with the signature or function name, the IDE and the compiler cannot highlight the error because we have not used an interface.
Conclusion
Angular components are fundamental to the library. In this chapter, we began by providing DOM tree context on a web page. HTML elements organized as a tree were introduced.
Next, I discussed the components and how to create them using Angular CLI. The ng generate command was used to create a component. It listed the files needed for building a component and their purposes.
Next, I elaborated on the various fields and configurations with the components. I explained the @Component TypeScript decorator. The decorator configures HTML templates and CSS style sheets as parameters. The TypeScript class encapsulates the presentation logic written in the TypeScript language.
Then we covered dependency injection and providing a TypeScript class or a service to the component. The component uses and depends on the TypeScript class or a service. A block of reusable code without a view can be encapsulated in the service.
The chapter concluded by discussing how components accept input, and use @Input decorator, provide output to the environment, and use @Output decorator with an event emitter.
References
Angular documentation on components (https://angular.io/api/core/Component#viewproviders)
Code Craft blog: NgModule.providers vs. Component.providers vs. Component.viewProviders (https://codecraft.tv/courses/angular/dependency-injection-and-providers/ngmodule-providers-vs-component-providers-vs-component-viewproviders/)
Exercise
Dinosaur 1
Herrerasaurus
A large predator
Herrerasauridae family
Dinosaur 2
Triceratops
An herbivorous dinosaur
Ceratopsidae family
Dinosaur 3
T. rex
Tyrannosaurus rex is abbreviated as T. rex.
Tyrannosauridae family
Dinosaur 4
Plateosaurus
The name means a broad lizard.
Plateosauridae family
Show a picture of the dinosaur.