Building Reusable Component In Angular - Part Five

Introduction

Today, we’ll learn how to use reusable components in Angular. So here, we’ll see how to pass the data from component to Views and we’ll raise the custom events. We’ll apply the styles to the HTML elements in the template.

But before getting started, make sure that you know about different building blocks of Angular as well as different variations of bindings in Angular. And here is the roadmap for Angular.

In this article, we’ll cover

  • Component API 
  • Input Properties
    • Aliasing Input Properties
  • Output Properties
  • Passing Event Data
  • Aliasing Output Properties
  • Templates
  • Styles
  • View Encapsulation
    • Shadow DOM
  • ngContent
  • ngContainer
  • What we have learned
  • Conclusion

Component API

In the last article, we’ve learned property and event binding.

Property Binding (Square Bracket Syntax):

  1. <img [src]=”imageUrl” />  

It is used to bind the DOM objects to fields or properties in a host component. Here is the component that is using DOM objects; in this case, img object.

Another way of thinking about this statement is that this src property is an input into this DOM object. We use this to supply data to this object, supply some state.

Similarly, we use event binding respond to the events raised from the DOM object.

  1. <button (click)=”onClick()”></button>  

In this case, the click event of the button. But the star component we implemented in the last article doesn’t have any property and event binding.

<star></star>

It isn’t reusable. Ideally, I want to set the initial state of this star component using some object that we have in the host component.  Here, in the app component, let’s say we get the post object from the server.
  1. @Component({  
  2.   selector: 'app-root',  
  3.   templateUrl: './app.component.html',  
  4.   styleUrls: ['./app.component.css']  
  5. })  
  6. export class AppComponent {  
  7.   post = {  
  8.     title: "Title",  
  9.     isFavorite: true  
  10.   }  
  11. }   

Input Properties

So, here in the star component, we want to mark the isFavorite field as an input property. So we can use it in property binding expression. And now, there are 2 ways to mark this field as an input property.

  1. <star [isFavorite]="post.isFavorite"></star>  

Here, unfortunately, we can’t use property binding to bind isFavorite field of star component to the post object. This is not going to work. Now just open your browser and here we’ll see this error.

So this property binding doesn’t work perfectly. Though in the star component, we have the isFavorite field and also it is a public field because in the Angular template in order for you to use property binding, you need to define the property of that field as an input property. To make star component more reusable, we want to add support for property and event binding, i.e., in the future, we want to be notified whenever the user clicks on the star component; i.e., we have got the click event here and we want to be notified and call the method in the host component (app component).
  1. <star [isFavorite]="post.isFavorite" (click)="onClick()"></star>  

And inside that method, we can call the server or do something else. Once again, we need to add support for event binding as well. We need to define the special property in star component that can be referred to as output property.

In other words, in order to make a component more reusable, we need to add a bunch of input and output properties. We use input properties to pass input or state to a component and we use output property to raise events from this custom component. A combination of input and output properties for a component make up what we call Component API (public API of that component).

 

So now, we don’t have component API in star component because it doesn’t have any input and output properties.

Input Properties

So here in star component, we want to mark the isFavorite field as an input property. So we can use it in property binding expression. There are 2 ways to mark this field as an input property.

  1. import { Component, OnInit, Input } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'star',  
  5.   templateUrl: './star.component.html',  
  6.   styleUrls: ['./star.component.css']  
  7. })  
  8. export class StarComponent implements OnInit {  
  9.   
  10.   @Input() isFavorite: boolean;  
  11.   
  12.   constructor() { }  
  13.   
  14.   ngOnInit() {  
  15.   }  
  16.   
  17.   onClick(){  
  18.     this.isFavorite = !this.isFavorite;  
  19.   }  
  20. }   
So here we use Input decorator on isFavorite field to expose this field for property binding. And Input decorator is from Input import package in @angular/core library. @Input is another component for marking fields and properties as input property. So let’s go back in the browser,
 

By default, it is filled because we have to bind our property with the app component post's isFavorite property and we set it to true there. So it is filled. Now our property binding works perfectly.

Now the 2nd approach is to make input property. We use Inputs property in Component declaratory.

  1. import { Component, OnInit } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'star',  
  5.   templateUrl: './star.component.html',  
  6.   styleUrls: ['./star.component.css'],  
  7.   inputs: ['isFavorite']  
  8. })  
  9. export class StarComponent implements OnInit {  
  10.   
  11.   isFavorite: boolean;  
  12.    
  13.   constructor() { }  
  14.   
  15.   ngOnInit() {  
  16.   }  
  17.   
  18.   onClick(){  
  19.     this.isFavorite = !this.isFavorite;  
  20.   }  
  21. }  
We can make our field property as input property in the declaratory but there is an issue with this approach. But first of all let’s run the application again and it is working fine. So the problem with this approach is magic string. So this code works as long as we have a field or property called isFavorite if in future, we decide to rename the property of isFavorite to something else due to refactoring reasons.
  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styleUrls: ['./star.component.css'],  
  5.   inputs: ['isFavorite']  
  6. })  
  7. export class StarComponent implements OnInit {  
  8.   
  9.   isSelected: boolean;  
  10.    
  11.   constructor() { }  
  12.   
  13.   ngOnInit() {  
  14.   }  
  15.   
  16.   onClick(){  
  17.     this.isSelected = !this.isSelected;  
  18.   }  
  19. }   
So our Input property will be broken. Now if you save the file and run the application in browser. Here we’ll see this result now,
 
But if you click on star it doesn’t go to empty state, so actually what’s happening under the hood is you have to make the isFavorite input property in Component declarator and it doesn’t exist in the star component. So Angular automatically create the isFavorite input property under the hood. And in app.component.html we’re binding isFavorite property to post.isFavorite
  1. <star [isFavorite]="post.isFavorite" (click)="onClick()"></star>  

And the value of isFavorite in app component is true. That’s it;  just in fill state, click event is not applying on it because click event is changing the state of isSelected, not isFavorite.

So that’s why it is a bad approach although it is quite clean instead of the first approach but it is not recommended. Most of the time when the language contains multiple ways to do anything then it confuses the beginners in learning. So the best approach to make the input property is by using Input declarator. So this is how we define Input properties.

Aliasing Input Properties

We’ve learned how to define the input properties, now let’s explore how to define the aliasing of input properties.

  1. export class StarComponent implements OnInit {  
  2.   @Input() isFavorite: boolean;  
  3. }  
Look here we defined isFavorite field using camel casing notation. Now maybe in your applications you don’t want to use camel casing notation in your html markup. So back in app.component.html we want to use,
  1. <star [is-favorite]="post.isFavorite" (click)="onClick()"></star>   

Look now we’ve changed the property as is-favorite. Now in Javascript and in Typescript we don’t have this kind of acceptable identifier because we can just make the variable without dash in JS.

So the solution to this kind of problem is to use the alias or a nickname for an input property. So back in star component here we’re using Input declaratory and here we’ll supply the string for the alias of this property.
  1. @Input('is-favorite') isFavorite: boolean;   

And now if you run the application, you’ll see it is working without any issue.

This aliasing actually gives the contract of this API stable. Let’s go back to app component and revert back.

  1. export class StarComponent implements OnInit {  
  2.   
  3.   @Input() isFavorite: boolean;  
  4.    
  5.   constructor() { }  
  6.   
  7.   ngOnInit() {  
  8.   }  
  9.   
  10.   onClick(){  
  11.     this.isFavorite = !this.isFavorite;  
  12.   }  
  13. }  
Now again rename isFavorite to isSelected.
  1. export class StarComponent implements OnInit {  
  2.   
  3.   @Input() isSelected: boolean;  
  4.   
  5.   onClick(){  
  6.     this.isSelected = !this.isSelected;  
  7.   }  
  8. }   
And now our application is in broken state. Because app component is not expecting an input property called isFavorite.
 
And here we’re getting this error again in the browser console. Now to minimize the impact of these changes, we can use alias to keep the contract of a component stable. So back in our star component
  1. @Input('isFavorite') isSelected: boolean;  
And now app.component will continue to use component like before. Now let’s try it and now we if you see we don’t have any errors in the browser
 
Look it is not filled, now it means that there is a problem inside the application. Now we already know star component is a standalone component and its implementation definition is in its star.component.html and we’re just using this star component in app.component.html by just using its selector. And if you go into star.component.html here we’re still using our previous isFavorite property which doesn’t exist anymore. So we need to update our component view as well.
  1. <span class="glyphicon"  
  2.       [class.glyphicon-star]="isFavorite"  
  3.       [class.glyphicon-star-empty]="!isFavorite"  
  4.       (click)="onClick()"  
  5. ></span>  
Here we’re still using isFavorite property. So need to change it here again as isSelected
  1. <span class="glyphicon"  
  2.       [class.glyphicon-star]="isSelected"  
  3.       [class.glyphicon-star-empty]="!isSelected"  
  4.       (click)="onClick()"  
  5. ></span>   
So what the point is when we updated the field, it doesn’t change the variable names in template. We need to do it manually. However because we’re using alias in Input property, we don’t have to go and change another 100 places in code where we’ve used this component. This is how we’re minimizing the change of impact.

Output Properties

So now we want to be notified when the user clicks on star. So here we want to raise the custom event ‘change’ and define them in the star component.
  1. <star [isFavorite]="post.isFavorite" (change)="onStarChange()"></star>  
And this click event will be called whenever star is clicked. Now open you app.component.ts to define onStarChange() method.
  1. export class AppComponent {  
  2.   post = {  
  3.     title: "Title",  
  4.     isFavorite: true  
  5.   }  
  6.   
  7.   onStarChange(){  
  8.     console.log('Star Changed');  
  9.   }  
  10. }  
Now you might be confused about why we’re defining our onStarChange() method here in app component. We’re doing these kind of things in our star component in our last article. Actually we want to make star component properties and methods reusable that we can reuse them by other component properties and methods. Although we’ve already defined the onClick() method in Star Component but here now we’re making change event output property and make it reusable by any other component method. Let’s come back to the point.

Now it is the time to make the change event output property. As we use Input declarator, similar here we use @Output declaratory and we’ll initialize @Output declarator variable with EventEmitter() it actually helps us to raise and publish the events.

  1. import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'star',  
  5.   templateUrl: './star.component.html',  
  6.   styleUrls: ['./star.component.css']  
  7. })  
  8. export class StarComponent implements OnInit {  
  9.   
  10.   @Input('isFavorite') isSelected: boolean;  
  11.   @Output() change = new EventEmitter();  
  12.    
  13.   constructor() { }  
  14.   
  15.   ngOnInit() {  
  16.   }  
  17.   
  18.   onClick(){  
  19.     this.isSelected = !this.isSelected;  
  20.     this.change.emit();  
  21.   }  
  22. }  

And here is the star component code. We need to call the emit() function with click output property. Now if we run the application 

 

Look it is working fine on star click as we’re printing the console message on Star click.

Passing Event Data

So here we’ll see how to pass event data when raising an event. Here in app component currently we’re just displaying a single message on console.
  1. onStarChange(){  
  2.   console.log('Star Changed');  
  3. }  

Here we don’t know anything about this event. We’re just raising an event which is displaying the message on console. We even don’t know if the user has marked the object as favorite or not. So we need to change our implementation and pass some data when raising an event. So back in our star component, when we’re emitting the event we can optionally pass some value and this value will be available to all the subscribers of this event. And here in our case, change event subscriber is app component onStarChange() function.

  1. export class StarComponent implements OnInit {  
  2.   
  3.   @Input('isFavorite') isSelected: boolean;  
  4.   @Output() change = new EventEmitter();  
  5.    
  6.   constructor() { }  
  7.   
  8.   ngOnInit() {  
  9.   }  
  10.   
  11.   onClick(){  
  12.     this.isSelected = !this.isSelected;  
  13.     this.change.emit(this.isSelected);  
  14.   }  
  15. }  

Look here we’re passing isSelected property to change event. Now we’re consuming this star component into app component. So open app.component.ts and make some changes.

  1. export class AppComponent {  
  2.   post = {  
  3.     title: "Title",  
  4.     isFavorite: true  
  5.   }  
  6.   
  7.   onStarChange(isFavorite){  
  8.     console.log('Star Changed ', isFavorite);  
  9.   }  
  10. }  

So we’re just catching the parameter and displaying it into console. And now open app.component.html

  1. <star [isFavorite]="post.isFavorite" (change)="onStarChange($event)"></star>  
And here we pass $event, we already know about $event. It is used to get the event information. Handling the click event of buttons, we use $event object which is the built-in object in Angular. Now previously when handling the click event of buttons, this $event represented a standard DOM event object here. Because we’re dealing with custom component, this $event would be anything we pass when raising an event. And in this case, it is just boolean value. Let’s try this,

 
This is how we pass the data along with events. Now let’s make the scenario more complex and instead of passing just the boolean value let’s pass the object in emit function.
  1. onClick(){  
  2.   this.isSelected = !this.isSelected;  
  3.   this.change.emit({ newValue: this.isSelected });  
  4. }  

And now here $event object is representing a Javascript object that has the property called newValue.

  1. <star [isFavorite]="post.isFavorite" (change)="onStarChange($event)"></star>  
And also in our app.component.ts which is the subscriber of this event, here we receive an actual object in function parameter.
  1. onStarChange(eventArgs){  
  2.   console.log('Star Changed ', eventArgs);  
  3. }  
I’ve just renamed the parameter name. Now let’s run the application again and here we’ll see the results.
 
 

You might be working in some kind of complex project and you want to show the intellisense while you’re working or you want compile time checking. So you need to apply the data annotations for the parameters.

  1. onStarChange(eventArgs: { newValue: boolean }){  
  2.   console.log('Star Changed ', eventArgs);  
  3. }  

Look here with the inline annotation we’ve applied the type of the parameter. But it is little bit messy. Now if you remember typescript, we’ve already discussed interfaces. With the help of interface, we can make our implementation more robust and we can make our code clean.

  1. import { Component } from '@angular/core';  
  2. import { debug } from 'util';  
  3.   
  4. interface StarChangedEventArgs {  
  5.   newValue: boolean;  
  6. }  
  7.   
  8. @Component({  
  9.   selector: 'app-root',  
  10.   templateUrl: './app.component.html',  
  11.   styleUrls: ['./app.component.css']  
  12. })  
  13. export class AppComponent {  
  14.   post = {  
  15.     title: "Title",  
  16.     isFavorite: true  
  17.   }  
  18.   
  19.   onStarChange(eventArgs: StarChangedEventArgs){  
  20.     console.log('Star Changed ', eventArgs);  
  21.   }  
  22. }  
Now it supports compile time checking and intellisense. If you want to see intellisense then you can,
 
 
 
Now if you’re building reusable component, you want to declare this interface in your implementation and export it from your module and then any consumer would import this from your module. So instead of declaring this interface in App component, we should place it into Star component and make it export
  1. import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'star',  
  5.   templateUrl: './star.component.html',  
  6.   styleUrls: ['./star.component.css']  
  7. })  
  8. export class StarComponent implements OnInit {  
  9.   
  10.   @Input('isFavorite') isSelected: boolean;  
  11.   @Output() change = new EventEmitter();  
  12.    
  13.   constructor() { }  
  14.   
  15.   ngOnInit() {  
  16.   }  
  17.   
  18.   onClick(){  
  19.     this.isSelected = !this.isSelected;  
  20.     this.change.emit({ newValue: this.isSelected });  
  21.   }  
  22. }  
  23.   
  24. export interface StarChangedEventArgs {  
  25.   newValue: boolean;  
  26. }  
And then in app component, we resolve the type reference issue here.
  1. import { StarChangedEventArgs } from './star/star.component';  
  2. import { Component } from '@angular/core';  
  3. import { debug } from 'util';  
  4.   
  5. @Component({  
  6.   selector: 'app-root',  
  7.   templateUrl: './app.component.html',  
  8.   styleUrls: ['./app.component.css']  
  9. })  
  10. export class AppComponent {  
  11.   post = {  
  12.     title: "Title",  
  13.     isFavorite: true  
  14.   }  
  15.   
  16.   onStarChange(eventArgs: StarChangedEventArgs){  
  17.     console.log('Star Changed ', eventArgs);  
  18.   }  
  19. }  

To resolve the reference file issue, auto import extension helps us a lot really. And on the opposite side our application is working fine. 

Aliasing Output Properties

So earlier we’ve learned how we can use an alias to keep the contract of the component stable. Previously we’ve used an alias for an input property but we can also use an alias on an output property.

  1. export class StarComponent implements OnInit {  
  2.   
  3.   @Input('isFavorite') isSelected: boolean;  
  4.   @Output() change = new EventEmitter();  
  5.    
  6.   constructor() { }  
  7.   
  8.   ngOnInit() {  
  9.   }  
  10.   
  11.   onClick(){  
  12.     this.isSelected = !this.isSelected;  
  13.     this.change.emit({ newValue: this.isSelected });  
  14.   }  
  15. }  

So here, tomorrow if we decide to change the name of this event from change to something else our application will certainly break or might not work properly. Let’s take an experiment.

  1. export class StarComponent implements OnInit {  
  2.   
  3.   @Input('isFavorite') isSelected: boolean;  
  4.   @Output() click = new EventEmitter();  
  5.    
  6.   constructor() { }  
  7.   
  8.   ngOnInit() {  
  9.   }  
  10.   
  11.   onClick(){  
  12.     this.isSelected = !this.isSelected;  
  13.     this.click.emit({ newValue: this.isSelected });  
  14.   }  
  15. }  

Now look, the subscriber of Star component is still expecting a change event.

  1. <star [isFavorite]="post.isFavorite" (change)="onStarChange($event)"></star>  

And if you run the application, our component is working properly but doesn’t show the log messages in the console which we’re displaying through onStarChange() event of Star component.

Now you might be thinking that app.component.html contains change event which doesn’t exist then why isn't our application broken. It is because our app component is expecting that it may exist sometimes in the future that’s why it doesn’t give us any error in the browser. But the code actually is broken because event handler onStarChange() is not called. So always it is the better choice to use alias to make sure if in the future we rename this field, the subscriber of the component is not going to break. So,

  1. export class StarComponent implements OnInit {  
  2.   
  3.   @Input('isFavorite') isSelected: boolean;  
  4.   @Output('change') click = new EventEmitter();  
  5.    
  6.   constructor() { }  
  7.   
  8.   ngOnInit() {  
  9.   }  
  10.   
  11.   onClick(){  
  12.     this.isSelected = !this.isSelected;  
  13.     this.click.emit({ newValue: this.isSelected });  
  14.   }  
  15. }  

And now if we run the application, we’ll see it is working fine. And our onStarChange() method is logging the message in console.

Templates

We’ve seen 2 forms of using template. One way is to use the template externally and then we use the templateUrl property of component metadata to specify the path to the template file and another approach is to use template property. So we can add the template inline here. Now please don’t mix these approaches, you can follow only 1 approach.

  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   template: '',  
  5.   styleUrls: ['./star.component.css']  
  6. })  

Now you might ask which approach is better.

It really depends, if you’re building a small component with a very simple template we can add the template here in the component declarator which would be easy to work with and to import it into multiple applications. Yes you can use your component into multiple applications. Just copy your component folder and paste it into any application where you want and just use it like we do here in app component. If your template is more than 5 lines of code, you can think your own that this code is quite busy and too noisy. So in that case the best approach is to store it in the external file.

Now you might think that as it is a separate file so there will be a new request for template files. Actually that’s not the case here. Remove the redundant template.

  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styleUrls: ['./star.component.css']  
  5. })  

Open your app.component.html and place a heading here with any name.

  1. <h2>Usama Shahid</h2>  
  2. <star [isFavorite]="post.isFavorite" (change)="onStarChange($event)"></star>  
Now back in the browser, in Chrome developer tools under the Network tab there are multiple requests to the server. And if you see in the list there is no request of any of our template files.
 
 
Our template files are actually bundled along with our source code. So here we have main.js, so all our typescript code is transpiled into javascript and placed inside this bundle. Now click on main.js and go to the response tab and here you’ll see the content of main bundle
 

Look all of our external templates are actually bundled along with our javascript code. So there is no separate request to the server to download the template files.

Styles

As we build components, sometimes we need to apply styles on the components. In Angular there are 3 ways to apply styles to a component but first of all let’s remove the unnecessary code.
  1. export class StarComponent {  
  2.   
  3.   @Input('isFavorite') isSelected: boolean;  
  4.   @Output('change') click = new EventEmitter();  
  5.    
  6.   onClick(){  
  7.     this.isSelected = !this.isSelected;  
  8.     this.click.emit({ newValue: this.isSelected });  
  9.   }  
  10. }  

We’ll see OnInt interfaces later, at the moment we don’t need it. So there are 3 ways to apply styles.

To use styles in external files and define it in Component declarator metadata. So here we’ve an array where we can place multiple css files.
  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styleUrls: ['./star.component.css']  
  5. })  
Th other way is by using styles property. It is similar to stylesUrls. We put the array here as well. And here we use backticks to use the multiple lines for styling purposes. And with this approach we can use inline styles in this component and this is exactly writing template inline. So if you’re working with small components with only a few styles, you may choose to write your styles here rather than in an extra noise stylesheet.
  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styleUrls: ['./star.component.css'],  
  5.   styles: [  
  6.     `  
  7.       
  8.     `  
  9.   ]  
  10. })  

Now you might want to ask why we put array syntax here, why not just a simple string. Honestly I don’t know the reason of this design. If you know then comment here and let me know as well.

And the interesting thing is Angular doesn’t limit you to use only 1 option among both of them, you can use styleUrls and styles as well. Last style will affect the html element in the browser.

And the third way to apply the styles is by writing it in our html template. However it is not the good approach but angular allows you to follow this approach. Now it is up to you what you prefer.
  1. <style>  
  2.   
  3. </style>  
  4.   
  5. <star [isFavorite]="post.isFavorite" (change)="onStarChange($event)"></star>  

So let’s see how the styles are overriding each other. So first of all let’s open the external stylesheet and apply the styles here.

 

And now open star component and apply the style in component declaratory

  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styleUrls: ['./star.component.css'],  
  5.   styles: [  
  6.     `  
  7.       .glyphicon {  
  8.         color: green;  
  9.       }  
  10.     `  
  11.   ]  
  12. })  

Now when we run the application, our icon will be green because we apply this last.

However if we change the arrangement of reference style files,

  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styles: [  
  5.     `  
  6.       .glyphicon {  
  7.         color: green;  
  8.       }  
  9.     `  
  10.   ],  
  11.   styleUrls: ['./star.component.css']  
  12. })  

Then obviously our icon becomes red. Because external style file css comes in last.

 
Now let me tell you one more thing. Angular actually picks only 1 style definition, the one that comes last. In other words, if we define the css in this way
  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styles: [  
  5.     `  
  6.       .glyphicon {  
  7.         color: green;  
  8.       }  
  9.   
  10.       .glyphicon-star {  
  11.         background: black;  
  12.       }  
  13.     `  
  14.   ],  
  15.   styleUrls: ['./star.component.css']  
  16. })  

Look here we define glyphicon and glyphicon-star and we’re just overriding glyphicon class in external stylesheet. So our result should be star with red color and black background color.

 
But the result is still in red. So the point is Angular is going to ignore completely all the styles and just follow the styles which come in last. So our external stylesheet comes last that’s why it is just showing us a red star. And if you apply the styles in component html file then all the styles will be ignored and just this style will be applied.
  1. <style>  
  2.       .glyphicon {  
  3.             color: blue;  
  4.       }  
  5. </style>  
  6.   
  7. <span class="glyphicon"  
  8.       [class.glyphicon-star]="isSelected"  
  9.       [class.glyphicon-star-empty]="!isSelected"  
  10.       (click)="onClick()"  
  11. ></span>  

And here is the star.component.html code. Now if we run the application

Here we see blue color star.

Now irrespective of which approach you choose, what is interesting in Angular is that you can create the scope of these styles so that these styles can’t leak outside of these component templates. So if you’ve glyphicon somewhere else in the html document.
  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styles: [  
  5.     `  
  6.       .glyphicon {  
  7.         color: green;  
  8.       }  
  9.   
  10.       .glyphicon-star {  
  11.         background: black;  
  12.       }  
  13.     `  
  14.   ],  
  15.   styleUrls: ['./star.component.css']  
  16. })  

These styles will not be applied to these glyphicons.

View Encapsulation

So we’ve seen that the styles we’ve applied in Components are just scoped to this component but also how it works.

So here we need the concept of Shadow DOM. It’s basically a specification that enables DOM tree and style encapsulation.

Shadow DOM allows us to apply scoped styles to elements without bleeding out to the outer world. This is the new feature in browsers but not in old browsers. It is only supported in Safari 10 or higher and Chrome 53 or higher. Let’s see shadow DOM in action. Look at this piece of code here

  1. var el = document.querySelector('star');  
  2. el.innerHTML = `  
  3.   <style> h1 { color: red } </style>  
  4.   <h1>Usama</h1>  
  5. `;  

It’s the plain javascript code. Here we’re getting star element and applying the innerHtml which has style of h1 and h1 element inside.

Now the problem with this implementation is that this style leaks outside this element. So if we’ve another h1 somewhere else, it’s going to be red as well. And we don’t want that. We’re building components, and we’re going o apply some styles to this component only. Let’s say you want to use a component built by someone else, it might have defined some styles of those components and you bring that component into your application. And you don’t want those styles to override in your application that’s where Shadow DOM comes into play. We can change this code and use Shadow DOM with only 1 extra line of code.
  1. var el = document.querySelector('star');  
  2. var root = el.createShadowRoot();  
  3. root.innerHTML = `  
  4.   <style> h1 { color: red } </style>  
  5.   <h1>Usama</h1>  
  6. `;  

So this is the Shadow DOM. Now you might think what does it do  in Angular.

Here we’ve this concept called View Encapsulation
  1. import { Component, OnInit, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'star',  
  5.   templateUrl: './star.component.html',  
  6.   styleUrls: ['./star.component.css'],  
  7.   encapsulation: ViewEncapsulation.Emulated  
  8. })  
We define here a property called encapsulation which contains the enumeration. ViewEncapsulation enum has 3 members and Emulated is the default selected member.
  1. encapsulation: ViewEncapsulation.Emulated  
So with this mood Angular emulates the Shadow DOM. And it understands that most of the browsers don’t support Shadow DOM. So it has its own trick to apply scope to our styles. Let’s see how it works, open your app.component.html and place the code.
  1. <star [isFavorite]="post.isFavorite" (change)="onStarChange($event)"></star>  
  2.   
  3. <span class="glyphicon glyphicon-user"></span>  
Look here we’re using one component element and other element is glyphicon-user. Now open the browser
 
 

Look here in head section we’ve 4 styles.

  • 1st style is for bootstrap css
  • 2nd style is for global styles which we apply in style.css in our project.
  • 3rd style is for external files styles.
  • 4th style is which we apply in its own component html file.

But if you ook at things closely, Angular has attached attributes with the elements [_ngcontent-c1] and with these attributes style colors are set. And if you go down here you can see 2 spans with different attribute values. The css which you’ve applied in component and its selector has same attribute name but the span which I created manually in app.component.html has different attribute name however both spans have same class glyphicon and we’re applying css on the basis of glyphicon class but it is just working with the firrst span glyphicon and not working with the second glyphicon.

So when the attribute name and class name matches then the style will be applied on that specific element. And this is the Emulated View Encapsulation which is the default View Encapsulation mode. So Angular tries to emulate the concept of shadow DOM.

  1. encapsulation: ViewEncapsulation.Native  

Now the second value is Native. And with this instead of generating the attributes on elements dynamically, Angular uses the native Shadow DOM feature in the browser. And of course, it is not yet supported in all of the browsers right now. Let’s see how it works

Look here we’ve just 2 styles above in head section and 2 below above the span.glyphicon-star. Now it doesn’t have additional attributes.

If you can’t see the Shadow DOM here, press F1 in Elements or Console tab of your chrome in Developer tools. And make sure you’ve selected this option

Now look it doesn’t have additional attributes.

Why did our icon disappear? Because the only styles that apply to this element is the red and blue color. So none of the styles defined in bootstrap are applied to this element that’s why we don’t see the actual icon. Now if you really bring bootstrap stuff here, so just copy the bootstrap import statement from global style.css and paste it in star component css as well.
 

And now our blue icon is showing there.

And here above to the glyphicon-star, complete bootstrap styles are loaded and its custom style is also there. And this approach creates the performance problem as well. So it is not the recommended approach.

So go back here and remove the bootstrap import statement from component css file.
  1. encapsulation: ViewEncapsulation.None  

And now this statement means that we don’t have any need of ViewEncapsulation here. So the styles defined here will leak outside this template which means that the style will be applied on all the elements.

And obviously, you don’t want to  use it. So now you’ve an idea of View Encapsulation and as we said its by default selected property is Emulated which is the best option. So originally we really don’t care about encapsulation property. All this explanation was just for your knowledge. Now you know what View Encapsulation is. So just remove encapsulation property from here.
  1. @Component({  
  2.   selector: 'star',  
  3.   templateUrl: './star.component.html',  
  4.   styleUrls: ['./star.component.css'],  
  5. })  

ngContent

Imagine we want to build bootstrap panel component. So let’s create a new component called PanelComponent
  1. PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g c Panel  
  1. import { Component } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'bootstrap-panel',  
  5.   templateUrl: './panel.component.html',  
  6.   styleUrls: ['./panel.component.css']  
  7. })  
  8. export class PanelComponent {  
  9.   constructor() {}   
  10. }  

If you’re building ra eusable component then always prefix it with any word. Like here we do and prefix the selector with bootstrap. Now let’s go to the template of this panel.

Now here we use Zen Coding feature, look
  1. div.panel.panel-default>div.panel-heading+div.panel-body  
Now press tab and see what code is written by this single line.
  1. <div class="panel panel-default">  
  2.   <div class="panel-heading"></div>  
  3.   <div class="panel-body"></div>  
  4. </div>  

This is the generated markup code. Let’s put some labels here

  1. <div class="panel panel-default">  
  2.   <div class="panel-heading">Heading</div>  
  3.   <div class="panel-body">Body</div>  
  4. </div>  
Now comment your app.component.html code and use the bootstrap-panel html element tag there.
  1. <bootstrap-panel></bootstrap-panel>  

And you’ll see the results in the browser.

Now let’s make this diagram dynamic. So first of all remove the hard coded Heading and Body labels from panel.component.html.
  1. <div class="panel panel-default">  
  2.   <div class="panel-heading"></div>  
  3.   <div class="panel-body"></div>  
  4. </div>  

And now open app.component.html

So in order to set the Heading and Body, 1 way is to use the Property Binding. So we can define the input properties
  1. <bootstrap-panel [heading]="heading"></bootstrap-panel>  
But this syntax is little bit weird here because I don’t want to go in app.component.ts and define the heading property there. I just want to write my markup here in app.component.html. So instead of using property binding, we’ll use ngContent element. So let’s go back to the panel.component.html and here we need to add 2 injection points so the consumer of this panel component can provide content into those injection points.
  1. <div class="panel panel-default">  
  2.   <div class="panel-heading">  
  3.     <ng-content></ng-content>  
  4.   </div>  
  5.   <div class="panel-body">  
  6.     <ng-content></ng-content>  
  7.   </div>  
  8. </div>  
ng-content it is the custom element defined in Angular. We’re placing these elements because we want to place the content at these places dynamically. But now here we’ve 2 ng-content elements and we need to distinguish them, we need to give some kind of identifier to uniquely identify them. So we use the select attribute and set it to the css selector. We can reference a class, an id or an element. So here we're going to use css class. So if in the consumer of the panel component, we have an element that matches the selector which means an element with heading class that element is going to be placed right here instead of ng-content. In simple words ng-content is replaced with that element.
  1. <div class="panel panel-default">  
  2.   <div class="panel-heading">  
  3.     <ng-content select=".heading"></ng-content>  
  4.   </div>  
  5.   <div class="panel-body">  
  6.     <ng-content select=".body"></ng-content>  
  7.   </div>  
  8. </div>  
Now let’s go back to our app.component.html and here we’ll add the 2 divs with heading and body classes inside the bootstrap-panel element.
  1. <bootstrap-panel>  
  2.   <div class="heading">Heading</div>  
  3.   <div class="body">Body</div>  
  4. </bootstrap-panel>  
And we can even add complex html elements here as well.
  1. <bootstrap-panel>  
  2.   <div class="heading">Heading</div>  
  3.   <div class="body">  
  4.     <h2>Body</h2>  
  5.     <p>Hello, World!</p>  
  6.   </div>  
  7. </bootstrap-panel>  

Now let’s preview this in the browser.

 

So in this way we can provide custom content and reusable component.

ngContainer

Now let’s inspect the heading element.

 
Look here we’ve heading class with Heading label and this div is put into app component. Here we’re selecting the element with class and initializing it with Label value. So with this implementation when angular finds an element with the selector in this case .heading.
  1. <div class="panel panel-default">  
  2.   <div class="panel-heading">  
  3.     <ng-content select=".heading"></ng-content>  
  4.   </div>  
  5.   <div class="panel-body">  
  6.     <ng-content select=".body"></ng-content>  
  7.   </div>  
  8. </div>  
It is going to replace the ng-content with that element.
  1. <div class="panel panel-default">  
  2.   <div class="panel-heading">  
  3.     <div class="heading">Heading</div>  
  4.   </div>  
  5.   <div class="panel-body">  
  6.     <ng-content select=".body"></ng-content>  
  7.   </div>  
  8. </div>  
But sometimes we don’t want this additional div here, it’s just to make the extra noise in the markup. It would be nicer to have just label here.
  1. <div class="panel panel-default">  
  2.   <div class="panel-heading">  
  3.     Heading  
  4.   </div>  
  5.   <div class="panel-body">  
  6.     <ng-content select=".body"></ng-content>  
  7.   </div>  
  8. </div>  
So let’s see how we can achieve this. Again revert the changes and place ng-content there like it was before.
  1. <div class="panel panel-default">  
  2.   <div class="panel-heading">  
  3.     <ng-content select=".heading"></ng-content>  
  4.   </div>  
  5.   <div class="panel-body">  
  6.     <ng-content select=".body"></ng-content>  
  7.   </div>  
  8. </div>  
Let’s go back to the app component. And we just replace the div with ng-container.
  1. <bootstrap-panel>  
  2.   <ng-container class="heading">Heading</ng-container>  
  3.   <div class="body">  
  4.     <h2>Body</h2>  
  5.     <p>Hello, World!</p>  
  6.   </div>  
  7. </bootstrap-panel>  
This is another custom element in Angular. So at runtime Angular only takes the content of this ng-container. It’s not going to render this ng-container element in the DOM. It’s not going to be a div or something else. Let’s look at the results. Save the file and inspect the heading in browser.
 
 

Look now we don’t have additional markup, we’ve just label.

What We’ve Learned

So here we’ll build the twitter like feature. A heart symbol with number of likes and changing color of heart on clicking. So let’s get started.

First of all let’s make a component.

  1. PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g c like  

Now let’s go to like.component.html. And here we want heart icon. So,

  1. <span  
  2.      class="glyphicon glyphicon-heart"  
  3.      [class.highlighted]="isActive"  
  4.      (click)="onClick()">  
  5. </span>  
  6.   
  7. <span>{{ totalLikes }}</span>  

And if we run the application here is the output.

And here we need to apply some css in like.component.css 
  1. .glyphicon {  
  2.     color: #ccc;  
  3.     cursor: pointer;  
  4. }  
  5.   
  6. .highlighted {  
  7.     color: deeppink;  
  8. }  

Now come back to like.component.ts

  1. import { Component, Input } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'like',  
  5.   templateUrl: './like.component.html',  
  6.   styleUrls: ['./like.component.css']  
  7. })  
  8. export class LikeComponent {  
  9.   @Input('totalLikes') totalLikes: number;  
  10.   @Input('isActive') isActive: boolean;  
  11.   
  12.   onClick() {  
  13.     this.totalLikes += (this.isActive) ? -1 : 1;  
  14.     this.isActive = !this.isActive;  
  15.   }  
  16. }  

Now go back to app.component.ts and here we need to create the tweet object.

  1. import { Component } from '@angular/core';  
  2. import { debug } from 'util';  
  3.   
  4. @Component({  
  5.   selector: 'app-root',  
  6.   templateUrl: './app.component.html',  
  7.   styleUrls: ['./app.component.css']  
  8. })  
  9. export class AppComponent {  
  10.   tweet = {  
  11.     likesCount: 10,  
  12.     isLiked: true  
  13.   }    
  14. }  
Now finally go back to app.component.html and here we use property binding syntax.
  1. <like  
  2.     [totalLikes]="tweet.likesCount"  
  3.     [isActive]="tweet.isLiked"  
  4. ></like>  

Now let’s test the results

 

And they’re working fine.

Conclusion

Here we’ve seen component APIs as input and output properties. We use these component APIs for reusing it again in our custom component as well. With the help of Input properties we can take the parameter values and with output properties we handle the event handlers. We have seen how to make the alias of Input and Output properties and how much they are useful for us. Because if we don’t apply alias and when we refactor our code then the code will be broken. So always it is the best approach to use alias. We’ve seen template and templateUrl where we define our component html. And then we just place our component selector in the html where we want to render this component. We’ve seen how we can apply the styles and how Angular follows the Shadow DOM inside View Encapsulation. This is how we can separate the things of different components. And they never override each other. Here we’ve seen how we can pass the data to the events. We’ve discussed ng-Content directive and how we use it to generate the dynamic html. But with ng-content there are different others html elements also created in the DOM which makes our code little bit dirty because actually these html elements are unnecessary. We don’t really need them so we use ng-container. With the help of ng-container we just render the content, and if we inspect the element we’ll see our code is very clean.

This is how the things works and we make the reusable components in Angular.


Similar Articles