Install the Angular CLI:
ng install -g @angular/cli
Create new project:
ng new my-app
Run:
cd my-app
ng serve --open
Components are the basic part of Angular anatomy project - they present the layout.
Usually, each folder contains the files of this component.
app.component
is the main component and it contains the app.module.ts
.
To create a new component, create a folder and its files:
newcomponent/
|-- newcomponent.component.css
|-- newcomponent.component.html
|-- newcomponent.component.ts
|-- newcomponent.component.spec.ts
Its primary configuration are located in the _.component.ts
file. In there, there should be the Component decorator.
import { Component } from "@angular/core";
@Component({
selector: "app-newcomponent",
templateUrl: "./newcomponent.component.html",
styleUrls: ["./newcomponent.component.css"],
})
export class NewComponent {}
selector
: which custom html element will render this component. It can be class, attribute or any other valid selector.template
ortemplateUrl
: required. What will this component renders. A string of the path should be passed ontemplateUrl
or the html elements ontemplate
.styles
orstyleUrls
: what styles will this component has. An array of strings of the paths should be passed onstyleUrls
or the style rules onstyles
.
The other files:
_.component.css
: styles of this component._.component.html
: html of the component._.component.spec.ts
: it holds the testing script.
Quick component creation
Complete and short version:
ng generate component newcomponent
ng g c newcomponent
Properties created inside the class component can be referenced in _.component.html
.
String interpolation
servers.component.ts
:
export class ServersComponent {
serverName = "test";
}
servers.component.html
:
<p>{{ serverName }}</p>
Property binding
servers.component.ts
:
export class ServersComponent {
allowNewServer = false;
}
servers.component.html
:
<button [disabled]="!allowNewServer">
Add Server
</button>
Event binding
servers.component.ts
:
export class ServersComponent {
// ...
onCreateServer() {
this.serverCreationStatus =
"Server was created! Name is " + this.serverName;
}
}
servers.component.html
:
<button (click)="onCreateServer()">
Add Server
</button>
<p>{{ serverCreationStatus }}</p>
Two-way-databinding
servers.component.html
:
<input type="text" [(ngModel)]="serverName" />
<p>{{ serverName }}</p>
Directives are instructions in the DOM. There are custom and built-in directives.
Structure directives They can add or remove elements.
<p *ngIf="serverCreated">
Server was created, server name: {{ serverCreationStatus }}
</p>
<app-server *ngFor="let server of servers"></app-server>
<div [ngSwitch]="value">
<p *ngSwitchCase="5">Value is 5</p>
<p *ngSwitchCase="10">Value is 10</p>
<p *ngSwitchDefault>Value is default</p>
</div>
Attribute directives Change the appearance or behavior of an element.
<p
[ngStyle]="{backgroundColor: getColor()}"
[ngClass]="{online: serverStatus === 'online'}"
>
Server with ID 10 is {{ status }}
</p>
In this example, both property values should receive a Javascript object. For ngStyle
, the property is the style (it can be camel or snake case), and the value is the value of the style. It is important to know that expressions are allowed as well.
For ngClass
, the property is the name of the class to be added and the value is the condition.
** Custom Directives**
The filename anatomy should be name-of-directive.directive.ts
, and there should be a @Directive
decorator.
import { Directive, ElementRef } from "@angular/core";
@Directive({
selector: "[appBasicHighlight]",
})
export class BasicHighlightDirective {
constructor(private elementReference: ElementRef) {}
}
In the app.module.ts
, there should be an import of the directive as well.
Some notes:
-
It is not recommended to change DOM properties directly in the directive, as there can be a rendering issue. The best practice is to use
Renderer2
. It should be used on any DOM manipulation.
import { Directive, OnInit, ElementRef, Renderer2 } from "@angular/core";
@Directive({
selector: "[appBetterHighlight]",
})
export class BetterHighlightDirective implements OnInit {
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
this.renderer.setStyle(
this.elRef.nativeElement,
"background-color",
"blue"
);
}
}
-
There is a way to create an event listener using the
@HostListener
decorator. And to bind a DOM property to the element, use the@HostBinding
.
import {
// ...
HostBinding,
HostListener,
} from "@angular/core";
export class CustomDirective {
@HostBinding("style.backgroundColor") backgroundColor: string = "transparent";
// ...
@HostListener("click") onClick(eventData: Event) {
// ...
}
}
-
To create structural directives, there are a couple of elements that can be used:
TemplateRef
to refer to the content in the DOM andViewContainerRef
to use its manipulation methods.
import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
@Directive({
selector: "[appUnless]",
})
export class UnlessDirective {
@Input() set appUnless(condition: boolean) {
if (!condition) {
this.vcRef.createEmbeddedView(this.templateRef);
} else {
this.vcRef.clear();
}
}
constructor(
private templateRef: TemplateRef<any>,
private vcRef: ViewContainerRef
) {}
}
<div *appUnless="onlyOdd">
<li *ngFor="let even of evenNumbers">{{ even }}</li>
</div>
- Project: 01-databinding
By default, properties inside a component are not shared between other ones. But, there is a way to pass values between parent and child (nested) components.
Passing values down to a child element
For example, if a app-server-element
(child) is nested in app.component
(parent), to pass info from parent to child:
app.component.html
<app-server-element
*ngFor="let serverElement of serverElements"
[srvElement]="serverElement"
></app-server-element>
[srvElement]="serverElement"
passes the serverElement
object to the app-server-element
.
In order to receive the value, the metadata file of the component must declare a @Input
decorator. The string parameter inside @Input()
is optional and refers to the alias of the element.
server-element.component.ts
import { Component, Input } from "@angular/core";
export class ServerElementComponent implements OnInit {
@Input("srvElement") element: { type: string; name: string; content: string };
// ...
}
Passing values up to a parent element
For example, if a app-cockpit
(child) is nested in app.component
(parent), and an event will be triggered in the child and pass information to its parent:
app.component.html
<app-cockpit (serverCreated)="onServerAdded($event)"></app-cockpit>
Note: the parameter has to be $event
to get the data back from the child component.
app.component.ts
export class AppComponent {
serverElements = [];
onServerAdded(serverData: { serverName: string; serverContent: string }) {
this.serverElements.push({
type: "server",
name: serverData.serverName,
content: serverData.serverContent,
});
}
}
cockpit.component.ts
export class CockpitComponent implements OnInit {
@Output() serverCreated = new EventEmitter<{
serverName: string;
serverContent: string;
}>();
newServerName = "";
newServerContent = "";
onAddServer() {
this.serverCreated.emit({
serverName: this.newServerName,
serverContent: this.newServerContent,
});
}
}
Angular has a feature similar to the Shadow DOM. While using it, we are sure that the element's style will not clash with other nodes of the DOM.
By default, ViewEncapsulation
is set to emulated
, which means Angular is encapsulating the components, but not using Shadow DOM, which is not supported by every browser.
To change its configuration, the @Component()
decorator has to be changed:
@Component({
// ...
encapsulation: ViewEncapsulation.None // it can be Emulated, None and ShadowDom
})
You can reference an element and a directive, for example, within a template. To do that, you need to add a reference to that target element, as #var
.
cockpit.component.html
<input type="text" class="form-control" #serverContentInput />
<button (click)="onAddServer(serverNameInput)">Add Server</button>
In this example, the input
element has a reference of serverContentInput
. This is passed as a parameter in the callback in the button click event.
ViewChild
is a decorator that watches a specific query - if the view DOM is changed and a new child matches the selector, the property is updated.
Anatomy: @ViewChild(selector, { static: true } )
. static
defines the time of the query resolution, which can be before (true
) or after (false
, default) change detection run.
We can specify that what it receives is a ElementRef
, an Angular type.
cockpit.component.html
<input type="text" class="form-control" #serverContentInput />
cockpit.component.ts
import { ViewChild, ElementRef, Component } from "@angular/core";
export class CockpitComponent {
@ViewChild("serverContentInput", { static: true })
serverContentInput: ElementRef;
// ...
}
Instead of using the same content in components, you can pass content between opening and closing tags of a component. This can be used calling <ng-content></ng-content>
placeholder.
app.component.html
<app-server-element
*ngFor="let serverElement of serverElements"
[srvElement]="serverElement"
>
<p #contentParagraph>
<strong *ngIf="element.type === 'server'" style="color: red"
>{{ element.content }}</strong
>
<em *ngIf="element.type === 'blueprint'">{{ element.content }}</em>
</p>
</app-server-element>
server-element.component.html
<div class="panel-body">
<ng-content></ng-content>
</div>
Accessing the #contentParagraph
local reference will not work, because it is not part of the server-element
view.
Instead of referencing via ViewChild
, ContentChild
has to be used. It is set on the ngAfterContentInit()
lifecycle hook.
Method | When it's called |
---|---|
ngOnChanges | after input property changes |
ngOnInit | once, when initialized |
ngDoCheck | during every change detection |
ngAfterContentInit | after content (ng-content) has been projected into view |
ngAfterContentChecked | every time projected content has been checked |
ngAfterViewInit | after component and child views initialized |
ngAfterViewChecked | every time view and child view have been checked |
ngOnDestroy | right before the component is destroyed |
Services are classes that are used to save or fetch data, and can be used across components. They can be injected in the component, which can be used by its child components, or it can be injected in the app.component.ts
, which makes this service available across all components.
example.service.ts
import { Injectable } from '@angular/core';
@Injectable()
export class ExampleService {}
If you want this service to be available across all components:
app.module.ts
import { ExampleService } from './services';
@NgModule({
providers: [ExampleService]
})
app.component.ts
@Component({
// ...
providers: [AccountsService]
})
export class AppComponent implements OnInit {
constructor(private accountServices: AccountsService){}
}
To use the service in a component, import the service, add to the providers
array in the @Component
decorator and initializes it into the constructor()
:
component-example.component.ts
import { ExampleService } from './services';
@Component({
// ...
providers: [ExampleService] // this will need to be added in case the service is not in the app.module.ts
})
export class ExampleComponent {
constructor(private exampleService: ExampleService) {}
}
It is important to know that if the service is providing data that you want to be accessible by other components, you should instanciate in the app.component.ts
or in a parent component. If there are multiple instances, then the data would not be shared.
Services in Angular 6+
There is a way of adding a service class globally in the app, instead of adding it into the providers[]
array, a parameter can be passed in @Injectable()
:
example.service.ts
import { Injectable } from '@angular/core';
@Injectable({providedIn: 'root'})
export class ExampleService {}
Although the other methods are still supported, this way allows the service to be lazy loaded, to increase performance.
Injecting services into services
another.service.ts
import { ExampleService } from './example.service';
@Injectable()
export class AnotherService {
constructor(private exampleService: ExampleService) {}
}