- npm Modules
- File Structure
- Architecture
- NgModules
- Components
- Templates
- Component Lifecycle Hooks
- Two-way Data Binding
- Services
- Asynchronous and Server-side Communication
- Framework code is distributed as npm modules:
-
@angular/animations
: Advanced animations functionality -
@angular/common
: Common utilities (pipes, structural directives etc.) -
@angular/compiler
: Ahead-of-Time compiler -
@angular/core
: Core functionality (always needed) -
@angular/forms
: Form handling -
@angular/language-service
: Language service for Angular templates for better IDE support -
@angular/platform-*
: Platform-specific modules (platforms: browser, server, webworker) -
@angular/router
: Routing functionality -
@angular/service-worker
: Service worker functionality -
@angular/upgrade
: NgUpgrade to upgrade from AngularJS -> Angular -
@angular/http
: Deprecated HTTP client
-
- Angular style guide declares set of rules
- Codelyzer (TSLint plugin) checks for
- File naming name.type.filetype:
- app.module.ts
- todos.component.ts
- todos.component.html
- todos.component.scss
- user.service.ts
- json.pipe.ts
- App needs to have at least one module
- Module has one root component
- Component can have child components
- Each application has single root NgModule
- NgModule is a class with
@NgModule
annotation - Declares collection of related elements (components, services etc.)
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
declarations
contains list of application build blocks, such as components, pipes and directives, with certain selectorimports
allows importing of other NgModules- For example
BrowserModule
imports browser-specific renderers and core directives such asngFor
andngIf
- For example
exports
allows declaring what is exported from this module (covered later)providers
contains list of services for dependency injectionbootstrap
contains root element(s) for the application (usually namedAppComponent
)
- We need to tell Angular to start our application
- This is done by providing root module
bootstrapModule
- This will go through the
bootstrap
array and checks for those selectors in HTML
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
index.html
<html>
<body>
<app-root></app-root>
</body>
</html>
- Modules are meant to offer possibility to divide the functionality in logical modules
- For example one could have
CustomerModule
,AdminModule
andBillingModule
- To access
exports
of another module, it needs to beimported
to the module:
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
*import { HttpClientModule } from '@angular/common';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, `HttpClientModule`],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
- Exporting allows declaring the public API provided by the module
- Consider company-specific UI module where
ListComponent
utilizesListRowComponent
:
ui.module.ts
import { NgModule } from '@angular/core';
import { ListComponent } from './list.component';
import { ListRowComponent } from './list-row.component';
@NgModule({
declarations: [ListComponent, ListRowComponent],
imports: [],
exports: [ListComponent],
providers: []
})
export class UiModule { }
- Directives
ngIf
andngFor
are defined actually inCommonModule
- But that is not imported anywhere?
- Actually,
BrowserModule
is exportingCommonModule
as seen in source - -> Modules can be exported as part of module
- If
ListModule
andTableModule
were separate modules:
ui.module.ts
import { NgModule } from '@angular/core';
import { ListModule } from './list.module';
import { TableModule } from './table.module';
@NgModule({
declarations: [],
imports: [],
exports: [ListModule, TableModule],
providers: []
})
export class UiModule { }
- Build blocks of application UI
- Has a template that it binds data to
- Can contain other components
- Class with
@Component
annotation
todos.component.ts
import { Component } from '@angular/core';
@Component({})
class TodosComponent { }
- Two parameters are mandatory for
@Component
annotation:- template with
template
(inline template) ortemplateUrl
(separate file) - selector (should always start with application specific prefix like the default:
app
)
- template with
- Components need to be declared in NgModule's declarations to be available in templates
todos.component.ts
import { Component } from '@angular/core';
@Component({
`templateUrl: 'todos.component.html'`,
`selector: 'app-todos'`
})
class TodosComponent { }
app.module.ts
@NgModule({
declarations: [AppComponent, `TodosComponent`]
...
})
export class AppModule { }
Browse to root of the project and run:
ng generate component todos
or abbreviated one:
ng g c todos
This will:
- Create a folder called
todos
with the component, template, styles and test file - Add the component to
declarations
of yourAppModule
- Plain HTML with few Angular specific additions*
todos.component.html
<h1>My todo application</h1>
- Selectors can be used to add other components
app.component.html
<h2>Todos</h2>
<app-todos></app-todos>
- Data binding with property name inside double curly braces ({{}})
{{property}}
- Structural directives with asterisk ( * ) followed by directive name
<div *ngIf="showItem">Item</div>
- Attribute binding with attribute name inside square brackets ([])
<input [disabled]="property" />
- Event binding with event name inside parenthesis
<div (click)="clickHandler()"></div>
- Template local variables with hash (#) followed by name
<input #nameInput />
Bind property from the component to the template
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-component',
templateUrl: 'app.component.html'
})
class AppComponent {
`title: string = 'app works!';`
}
app.component.html
<h1>`{{title}}`</h1>
- Modify the structure of the template
- Two most used are
*ngIf
and*ngFor
todos.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-todos',
templateUrl: 'todos.component.html'
})
class TodosComponent {
`todos: any[] = [{name: 'Do the laundry'}, {name: 'Clean my room'}];`
}
todos.component.html
<div `*ngFor="let todo of todos"`>
{{todo.name}}
</div>
Bind value from component into HTML attribute
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-component',
templateUrl: 'app.component.html'
})
class AppComponent {
`isDisabled: boolean = true;`
}
app.component.html
<input [disabled]="isDisabled" />
- Classes and styles have special syntax available:
<div [class.active]="isActive"></div>
<div [style.display]="isShown ? 'block' : 'none'"></div>
- Attribute binding only works for native properties of HTML elements by default
- Same concept can also be used to pass data from parent component to child component
parent.component.html
<app-child `[foo]="todo"`></app-child>
child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child'
})
class ChildComponent {
* @Input()
* foo: string;
}
- Register handler code for events
- The actual event can be referenced with
$event
- The events are basic DOM events (like
click
,mouseover
,change
andinput
)
todos.component.html
<input type="text" `(change)="handleChanged($event.target.value)"`></input>
todos.component.ts
import { Component } from '@angular/core';
@Component({..})
class TodosComponent {
* handleChanged(value: string) {
* // Do something with value
* }
}
- Event binding only works for native events of HTML elements by default
- Same concept can also be used to pass data from child component to parent component
parent.component.html
<app-child `(change)="todo"`></app-child>
child.component.ts
import { Component, Output } from '@angular/core';
@Component({
selector: 'app-child'
})
class ChildComponent {
* @Output()
* change = new EventEmitter<string>();
}
- Two-way data binding with
ngModel
inside banana-box syntax:[(ngModel)]
- Data flow is still unidirectional, though:
- value from component is updated to input on change
- when user modifies the value, it is updated to component
Name: <input type="text" `[(ngModel)]="name"` />
which is just sugar for
Name: <input type="text" `[ngModel]="name" (ngModelChange)="name = $event"` />
- Component can have styles applied:
- Inline in annotation (field
styles
in@Component
) - In external files (field
styleUrls
)
- Inline in annotation (field
- These styles are scoped for component -> No other component can get affected by them
todos.component.html
<div class="todos"></div>
todos.component.css
.todos {
background-color: red;
}
Does not affect styles here:
_clients.component.html _
<div class="todos"></div>
todos.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-todos',
templateUrl: 'todos.component.html',
`styles: ['.active-todo { background-color: yellow; }']`
})
class TodosComponent {
}
todos.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-todos',
templateUrl: 'todos.component.html',
`styleUrls: ['todos.component.css']`
})
class TodosComponent {
}
- For hooking into certain lifecycle events
- Interface for each hook
- Example hooks:
ngOnInit
(interfaceOnInit
),ngOnChanges
(interfaceOnChanges
) andngOnDestroy
(interfaceOnDestroy
)
import { Component, OnInit } from '@angular/core';
export class MyComponent `implements OnInit` {
`ngOnInit() { ... }`
}
ngOnInit
should be used for initialization of data for testability
- Module-wide singletons
- Used to store state, communicate with backends etc.
- Examples:
UserService
,BackendService
- Declaring:
user.service.ts
import { Injectable } from '@angular/core';
*@Injectable()
export class UserService {
}
- Need to be registered for NgModule
app.module.ts
@NgModule(
...
* providers: [UserService]
)
export class AppModule() {}
Browse to root of the project and run:
ng generate service todo
or abbreviated one:
ng g s todo
This will:
- Create a file called
todos.service.ts
in the root ofapp/
folder along with the test stub - not add it as provider in
AppModule
so you must do it yourself!
- Angular implements concept called
Dependency Injection
- In DI you can just ask for the dependencies as constructor parameters as follows:
todos.component.ts
import { Component } from '@angular/core';
*import { BackendService } from './backend.service';
@Component({
selector: 'app-todos',
templateUrl: 'todos.component.html'
})
class TodosComponent {
* constructor(private backendService: BackendService) {
* }
initializeData() {
this.backendService.makeRequest();
}
}
Angular will then pass the singleton instance of BackendService
to the component when creating it.
- Asynchronous things are modeled as Observables (covered later) in Angular
- For now, we only need to know that there is
subscribe
method
- For now, we only need to know that there is
- For AJAX requests, use
HttpClient
service found in@angular/common
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({...})
export class MyComponent {
filteredData: any[];
constructor(private httpClient: HttpClient) {
}
getData() {
* this.httpClient.get('https://example.com/mydata').subscribe(data => {
// Do stuff with data
this.filteredData = data.filter(item => item.id > 100);
})
}
}