The Component
metadata also supports several settings that contribute to easy template management and configuration. On the other hand, Angular 2 takes advantage of the CSS encapsulation functionalities of Web Components.
As our applications grow in size and complexity, chances are that our templates will grow as well, hosting other components and bigger chunks of HTML code. Embedding all this code in our component class definitions will become a cumbersome and unpleasant task and quite prone to errors by the way. In order to prevent this from happening, we can leverage the templateUrl
property, pointing to a standalone HTML file that contains our component HTML markup.
Back to our previous example, we can refactor the @Component
decorator of our PomodoroTimerComponent
class to point to an external html file containing our template. Create a new file named pomodoro-timer.html
in the same workspace where our pomodoro-timer.ts
file lives and populate it with the same HTML we configured in our PomodoroTimerComponent
class:
<div class="container text-center"> <img src="assets/img/pomodoro.png" alt="Pomodoro"/> <countdown [seconds]="25" (complete)="onCountdownCompleted()" #counter> </countdown> <p> <button class="btn btn-default"(click)="counter.seconds = 25"> Reset countdown to 25 seconds </button> </p> <p *ngIf="counter.seconds < 10"> Beware! Only <strong>{{ counter.seconds }} seconds</strong> left. </p> </div>
Now, we can polish our @Component
decorator to point to that file instead of defining the HTML inside the decorator metadata:
@Component({
selector: 'pomodoro-timer',
directives: [CountdownComponent],
templateUrl: './pomodoro-tasks.html'
})
class PomodoroTimerComponent {
// Class follows below...
}
External templates follow a certain convention in Angular 2, enforced by the most popular Angular 2 coding style guides out there, which is to share the same filename than the component they belong to, including any filename prefix or suffix we might append to the component filename. We will see this when exploring component naming conventions in Chapter 5, Building an Application with Angular 2 Components. This way, it is easier to recognize, or even search with your IDE search built-in fuzzy finder tool, what HTML file is in fact the template of a specific component.
What is the threshold for creating standalone templates rather than keeping the template markup inside the component? It depends on the complexity and size of the template. Common sense will be your best advisor in this case.
In order to better encapsulate our code and make it more reusable, we can define CSS styling within our components. These internal style sheets are a good way to make our components more shareable and maintainable. There are three different ways of defining CSS styling for our components.
We can define styles for our HTML elements and class names through the style property in the component decorator, like this:
@Component({ selector: 'my-component', styles: [` p { text-align: center; } table { margin: auto; }`] })
This property will take an array of strings containing CSS rules each and apply them to the template markup by embedding those rules at the head of the document as soon as we bootstrap our application. We can either inline the styling rules in a single line, or take advantage of ES2015 template strings to indent the code and make it more readable as depicted in the example above.
Just like styles
, styleUrls
will accept an array of strings, although each one will represent a link to an external style sheet though. This property can be used alongside the styles
property as well, defining different sets of rules where required:
@Component({ selector: 'my-component', styleUrls: ['path/to/my-stylesheet.css'], styles: [` p { text-align: center; } table { margin: auto; }`] })
All the preceding sections (styles
, styleUrls
, and inline style sheets) will be governed by the usual rules of CSS specificity (https://developer.mozilla.org/en/docs/Web/CSS/Specificity). CSS management and specificity becomes a breeze on browsers that support Shadow DOM, thanks to scoped styling. CSS styles apply to the elements contained in the component but do not spread beyond its boundaries.
On top of that, Angular will embed these style sheets at the head of the document, so they might affect other elements of our application. In order to prevent this from happening, we can set up different levels of view encapsulation.
In a nutshell, encapsulation is the way Angular needs to manage CSS scoping within the component for both Shadow DOM-compliant browsers and those that do not support it. For all this, we leverage the ViewEncapsulation
enum, which can take any of these values:
Let's check out an actual example. First, import the ViewEncapsulation
enum into the script, and then create an encapsulation
property with the Emulated
value. Then, let's create a style rule for our countdown text so any <h1>
(!) tag is rendered in dark red:
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { bootstrap } from '@angular/platform-browser-dynamic'; @Component({ selector: 'countdown', template: '<h1>Time left: {{seconds}}</h1>', styles: ['h1 { color: #900 }'], encapsulation: ViewEncapsulation.Emulated }) class CountdownComponent { // Etc... }
Now, click on the browser's dev tools inspector and check the generated HTML to discover how Angular 2 injected the CSS inside the page <HEAD>
block. The just injected style sheet has been sandboxed to ensure that the global CSS rule we defined at the component setup in a very non-specific way for all <h1>
elements only applies to matching elements scoped by the CountdownComponent
component exclusively.
We recommend that you try out different values and see how the CSS code is injected into the document. You will immediately notice the different grades of isolation that each variation provides.