# Components

Usually we have the AppComponent, which is the root of the app. Each component is split into 4 files:

  • HTML
  • CSS
  • TypeScript
  • spec.ts

Usually, each component has its own folder. We store them in the app folder (that is pregenerated by ng new) or some subfolders of it.

Here's the simplest component:

import { Component } from "@angular/core";

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html",
    styleUrls: ["./app.component.css"],
})
export class AppComponent {}

Angular promotes the separation of things into different files, opposed to frameworks like Vue.js. However, we are able to have HTML/CSS in the ts file simply by replacing templateUrl/styleUrls with template/styles and providing the HTML or CSS there as a string.

# selector

The selector is usually prefixed with app- to keep us from overwriting HTML tags.

The selector does not have to be an HTML tag. It can also be an attribute or a class:

  • selector: 'app-root' - it will match HTML elements <app-root></app-root>.
  • selector: '[app-root]' - it will match any HTML element with that attribue, e.g. <div app-root></div>.
  • selector: '.app-root' - it will match any HTML element that has the app-root class on it, e.g. <div class="app-root"></div>.

The selector property is actually not just the way to name the component. It is a specifier of where to render it. It's kind of similar to Kubernetes' Service object and its selector, which specifies where to send the traffic.

Most often we just specify our own tag for the component.

# Data Binding

# Output

We can output data with:

{{ something }}

The variable to output needs to exist on the component:

@Component({
    selector: "app-component",
    templateUrl: "./app.component.html",
})
export class AppComponent {
    something = 10;
}
s;

TIP

Inside of {{}} you can actually have any JS/TS expression, like a call to some method.

It can't contain if condition though.

# Property Binding

<button [disabled]="!allowNewServer">Add Server</button>

The disabled property of a button is controlled by the allowNewServer property on our component:

@Component({
    selector: "app-servers",
    templateUrl: "./servers.component.html",
    styleUrls: ["./servers.component.css"],
})
export class ServersComponent implements OnInit {
    allowNewServer = false;
}

innerText

Every tag has innerText property, which sets the content of the tag, e.g.:

<p [innerText]="Content"></p>

produces

<p>Content</p>

The {{}} would also work.

Brackets vs. no brackets

We don't always have to provide square brackets when binding to some @Input.

If brackets are there, the value is treated as an expression.

If there are no brackets the value is treated as a string.

# Two-way Binding

In forms, it is useful to bind in two-ways, so that we can control the contents of the input and also read that content.

Here's an example:

<input type="text" [(ngModel)]="username" />

I am able to access username in the TS code and I am also able to modify it.

# Events

We can bind to "native" events with (event)="expression". For example:

<button (click)="onButtonClick()">Add</button>

The onButtonClick method would have to exist on our component. We could also put some expression directly in the HTML instead of invoking a method.

# Event Details

We can pass an object with event details by including $event:

<button (click)="onButtonClick($event)">Add</button>

# Custom Events

Our comonents can emit custom events that the parent of the component can handle:



 


 



@Component(...)
export class MyComponent {
    @Output() userCreated = new EventEmitter<User>();

    someMethod() {
        this.userCreated.emit({name: 'Greg', age: 22});
    }
}

The parent comonent would attach to that event like this:

<app-my-component (userCreated)="onUserCreated($event)"></ap-my-component>

The onUserCreated would be some method defined on that parent. The $event would be the instance of User the MyComponent passed to the event.

TIP

EventEmitter<> and Output must be imported from @angular/core.

We can alias the name of the event similarly to how we can do that in Inputs that I will describe next.

# Inputs

In order to allow our components to accept "arguments", we need to define them, like this:



 


@Component(...)
export class MyComponent {
    @Input() name: string;
}

The parent component would pass data into MyComponent like this:

<app-my-component [name]="Steve"></app-my-component>

Aliasing

We can change the property's name that the parent sees with:

@Input('username') name: string;

Now, the parent has to bind to username, not name:

<app-my-component [username]="Steve"></app-my-component>

# Accepting Children

Our components may display some children that would be passed by the parent component. Here's how to do that.

The component that accepts children needs to have this somewhere in its template:

<ng-content></ng-content>

The children would be passed like that:

<app-some-component>
    <p>I am being passed into SomeComponent!</p>
</app-some-component>

If a component does not define <ng-content> anywhere in its template, any content that we pass into it is just lost.

# @ViewContent

We might need to access some HTML element that is passed to our component as a child. We can't do that with @ViewChild, because it can only be used for elements that belong to our component directly. There's another decorator though - @ViewContent.

Here's an example:

From the parent we pass some child into SomeComponent:

<app-some-component>
    <p #paragraph>Bla bla bla</p>
</app-some-component>

From the SomeComponent (that received the paragraph into its <ng-content>):

// The property can be called whatever we want
@ViewContent('paragraph') paragraph: ElementRef;

WARNING

If you're about to use the ElementRef from the ngOnInit hook, you should add { static: true }:

@ViewContent('paragraph', { static: true }) paragraph: ElementRef;

I don't really know why you'd even want to do that since the target element would not be even initialized yet. It will be initialized in the ngAfterContentInit though.

# Component Lifecycle

  • ngOnInit - fired once the component is initialized. It's not yet displayed, but the object for it was created in memory (the constructor finished its execution at this point). Accessing DOM elements from here (e.g. via references) might not give us the expected result since they might not be initialized yet. It's better to use ngAfterViewInit for that.
  • ngOnChanges - fired when the component is initialized and also each time any property with the @Input decorator is modified. It is the only hook that receives an argument - these are the changes that Angular found (with current and previous values).
  • ngDoCheck - run whenever Angular checks for changes in the component. Even if nothing really changed, Angular has to check it first, and this method will be called. The change detection is triggered by various things: events, Promise resolution and others.
  • ngAfterContentInit - called when the child passed to this component gets initialized (<ng-content>).
  • ngAfterContentChecked - fired when the Angular's change detection finishes checking the child component.
  • ngAfterViewInit - fired when the component gets rendered on the screen
  • ngAfterViewChecked - fired when the view (including children) has been checked by Angular.
  • ngOnDestroy - called once the component is about to be destroyed.

Imports

Each lifecycle method implementation requires our component to implement a specific type. For example, ngOnInit requires OnInit to be implemented.

# Instantiating Components from TypeScript

Normally, we include all the components that we want to render in our template .html files. However, in more dynamic scenarios, it might be impossible to know upfront which components will be needed on a given page. That's where Dynamic Components might be used. We're able to create components within our TypeScript code and render them on the page.

Here's how to do it: StackOverflow (opens new window).

In Angular <13, we'd use ComponentFactoryResolver. Nowadays, it's deprecated.

Last Updated: 1/15/2023, 6:32:34 PM