angular is a JavaScript Framework which allows you to create reactive Single-Page-Applications(SPAs).
npm install -g @angular/cli
// then go to the folder we want to create the project
ng new my-dream-app
cd my-dream-app
ng serve
// use bootstrap 2
npm install --save bootstrap@3
// in angular.json, styles
// add at front
"node_modules/bootstrap/dist/css/bootstrap.min.css",
ng add @angular/material
index.html file is served by server
replace with the template of the component. the template of this component is app.component.html
cli created the root component of the application under "app folder"
in app.component.ts
selector: 'app-root'
in main.ts, bootstrap start the application with AppModule
in app.module
app.module.ts: declaration: register the app component with angular
bootstrap: [AppComponent]
list all the components should be known to angular at the point of time it analyse index.html file.
angular thinks in "Components"
component is a typescript class.
Ideally, a component's job is to enable the user experience and nothing more. A component should present properties and methods for data binding, in order to mediate between the view (rendered by the template) and the application logic (which often includes some notion of a model).
other components, their selectors will be added to the app component from component decorator.
the other component stored in another folder. component declarations in app.module
ng generate component name
// or
ng g c name
// inside a component folder
ng g c folder/name
nesting components: inside one component call another component using their selectors multiple times
component selector is like CSS selector
select by elements
by attributes
by class
Simply put, data binding is a fundamental concept in Angular that allows developers to make communication between a component and its view or more precisly the DOM. This way you don't need to manually push data from your component to the DOM and back.
Angular provides four types of data binding and they are essentically different in the way data flows i.e from the component to the DOM, from the DOM to the component or both ways:
tag (For example:
). We use brackets for property binding.click
, is triggered, the bound method from the component is called. For example:
- The sayHi()
method will be called so it needs to be defined in the component class. We use parentheses for event binding. passing data, $event is the reserved name, access to event emitted data. when using data, event is in type Event
(The foobar
variable needs to be defined in the component). The input element and foobar
will have the same value and when one changes, the other one changes to the same value accordingly. We use the banana in the box syntax which combines brackets and parentheses for two-way data binding. ngModel
is a special directive that binds to the value
attribute of the
and
elements but you can constrcut two-way data binding for any property in the DOM or component. Two-way data binding = property binding + event binding. Note need to import FormsModule in app.module.ts.use decorator
// expose this property to the component
// bind the property from outside
@Input() element
// then the host component (through the selector) is able to bind the element
// in app.component.html
[element] = "serverElement"
// assigning alias to custom properties
@Input('srvElement) element
[srvElement] = "serverElement"
// define the type the eventemitter is going to emit
@Output() serverCreated = new EventEmitter<{serverName: string, serverContent: string}>();
// emit the new event of this type
this.serverCreated.emit({serverName: val, serverContent: val})
// can subscribe to the event
// Registers handlers for events emitted by this instance.
.subscribe(()=>{
})
// in app.component.html
// onServerAdded is in app.component.ts
// serverCreated is from cockpit.component.ts
// assign alias similar to property binding
View encapsulation defines whether the template and styles defined within the component can affect the whole application or vice versa. Angular provides three encapsulation strategies:
Emulated
(default) - styles from main HTML propagate to the component. Styles defined in this component's @Component
decorator are scoped to this component only.
@Component({
// ...
encapsulation: ViewEncapsulation.None,
styles: [
// ...
]
})
export class HelloComponent {
// ...
}
Instead of two-way binding, we can easily fetch a value of any input through local references in Angular.
can apply to any html element
// will hold the reference to the element (all the properties of the element)
// can use anywhere in the template (in html file, not in ts)
// in component.ts, serverContentInput is of type ElementRef
@ViewChild('name',{static: true}) serverContentInput: ElementRef;
// in component.html, local references
// to get access to the element through nativeElement
this.serverContentInput.nativeElement.value;
html pass into the component from outside
// anything between the opening and closing tag of the own component is lost
// use ng-content
// in server-element.component.html
// as a hook to mark the place for the content inbetween the tags
// in server-element.component.ts
// we want to access to the local reference defined in app.component.html through ng-content
@ContentChild('name' {static:true}) varname;
// in server-element.component.html
// in app.component.html, local references
Every Angular component and Angular directive have a lifecycle
Angular creates it, renders it, creates and renders it’s children, checks it when it’s data-bound properties change, and destroys it before removing it from the DOM.
*ngIf="posts.length > 0"
enchance ngif with else condition
local reference:
Server was created, server name is {{serverName}}
No server was created!
*ngFor="let post of posts"
get the index from ngFor
ngSwitch
Value is 5
Value is 0
@Directive({
selector: "[appUnless]"
})
export class UnlessDirecrive implement OnInit{
@Input() set appUnless(condition: boolean){
if(!condition){
this.vcRef.createEmbeddedView(this.remplateRef);
}else{
this.vcRef.clear();
}
}
constructor(private templateRef: TemplateRef, private vcRef: ViewContainerRef) {}
}
use *appUnless as directive
don't add or remove elements. they only change the element they were placed on.
use property binding with this directive.
ngStyle dynamically assign a CSS style.
ngClass allows dynamically remove CSS classes
@Directive({
selector:'[appBasic]'
})
export class BasicDirective{
}
//in app.module.ts add in declarations:
// in app.component.html
create directive
ng g d directivename
import{ Directive, OnInit, Renderer2 } from
@Directive({
selector: "[]"
})
export class DirecriveName implement OnInit{
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
ngOnInit(){
this.renderer.setStyle(this.elRef.nativeElement, 'background-color','blue');
}
}
another method instead of renderer
@HostBinding('property of hosting element we want to bind') property:type
@HostListener('eventname') method(eventData: Event){
}
use custom property binding
Both reactive and template-driven forms share underlying building blocks.
FormControl
tracks the value and validation status of an individual form control.FormGroup
tracks the same values and status for a collection of form controls.FormArray
tracks the same values and status for an array of form controls.ControlValueAccessor
creates a bridge between Angular FormControl
instances and native DOM elements. The source of truth provides the value and status of the form element at a given point in time. In reactive forms, the form model is the source of truth. In the example above, the form model is the FormControl
instance. With reactive forms, the form model is explicitly defined in the component class. The reactive form directive (in this case, FormControlDirective
) then links the existing FormControl
instance to a specific form element in the view using a value accessor (ControlValueAccessor
instance).
In template-driven forms, the source of truth is the template. The abstraction of the form model promotes simplicity over structure. The template-driven form directive NgModel
is responsible for creating and managing the FormControl
instance for a given form element. It's less explicit, but you no longer have direct control over the form model.
Updates from the view to the model and from the model to the view are synchronous and aren't dependent on the UI rendered.
The steps below outline the data flow from view to model.
FormControl
instance.FormControl
instance emits the new value through the valueChanges
observable.valueChanges
observable receive the new value.The steps below outline the data flow from model to view.
favoriteColorControl.setValue()
method, which updates the FormControl
value.FormControl
instance emits the new value through the valueChanges
observable.valueChanges
observable receive the new value.setValue()
method on the FormControl
instance.FormControl
instance emits the new value through the valueChanges
observable.valueChanges
observable receive the new value.NgModel.viewToModelUpdate()
method which emits an ngModelChange
event.favoriteColor
property, the favoriteColor
property in the component is updated to the value emitted by the ngModelChange
event (Blue).
The steps below outline the data flow from model to view when the favoriteColor
changes from Blue to Red.
favoriteColor
value is updated in the component.ngOnChanges
lifecycle hook is called on the NgModel
directive instance because the value of one of its inputs has changed.ngOnChanges()
method queues an async task to set the value for the internal FormControl
instance.FormControl
instance value is executed.FormControl
instance emits the latest value through the valueChanges
observable.valueChanges
observable receive the new value.favoriteColor
value.
Validation is an integral part of managing any set of forms. Whether you're checking for required fields or querying an external API for an existing username, Angular provides a set of built-in validators as well as the ability to create custom validators.
in app.module.ts
import { FormsModule } from '@angular/forms';
imports: [
FormsModule,
]
[ngModel] = ""
[(ngModel)] = ""
// use the element from the component
onSubmit(form: ngForm) {
}
@ViewChild('f') signupForm:NgForm;
onSubmit( {
console.log(this.signupForm);
}
angular dynamically add some CSS class, give information about the individual controls.
https://angular.io/api/forms/Validators
Set and Patch Form values
@ViewChild('f') signupForm:NgForm;
// set the whole form
this.signupForm.setValue()
// to overwrite parts of the form
this.signupForm.form.patchValue()
Use Form Data
this.signupForm.value.userData.xxx
Reset From
this.signupForm.reset();
// or reset with specific values
2.Reactive-driven
The FormControl
class is the basic building block when using reactive forms.
not configure the form in the template
import { ReactiveFormModule } from '@angular/forms';
imports:[
ReactiveFormsModule
]
Displaying a form control value
- Through the
valueChanges
observable where you can listen for changes in the form's value in the template using AsyncPipe
or in the component class using the subscribe()
method.
- With the
value
property, which gives you a snapshot of the current value.
Replacing a form control value
A form control instance provides a setValue()
method that updates the value of the form control and validates the structure of the value provided against the control's structure.
Partial model updates(patch)
-
Use the setValue()
method to set a new value for an individual control. The setValue()
method strictly adheres to the structure of the form group and replaces the entire value for the control.
-
Use the patchValue()
method to replace any properties defined in the object that have changed in the form model. PatchValue()
only updates properties that the form model defines.
updateProfile() {
this.profileForm.patchValue({
firstName: 'Nancy',
address: {
street: '123 Drew Street'
}
});
}
Grouping form controls
a form group instance tracks the form state of a group of form control instances (for example, a form). Each control in a form group instance is tracked by name when creating the form group.
import {FromControl,FormGroup} from '@angular/forms';
@Component()
export class AppComponent implements OnInit {
// declare type
signupForm: FormGroup;
ngOnInit(){
this.signupFrom = new FormGroup({
'username':new FromControl(null),
'email': new FormControl(null),
'gender': new FormControl('male')
});
}
}
The individual form controls are now collected within a group. A FormGroup
instance provides its model value as an object reduced from the values of each control in the group. A form group instance has the same properties (such as value
and untouched
) and methods (such as setValue()
) as a form control instance.
Associating the FormGroup model and view
A form group tracks the status and changes for each of its controls, so if one of the controls changes, the parent control also emits a new status or value change.
using formControl binding provided by FormControl Directive in Reactive Forms Module
submit the form
The FormGroup
directive listens for the submit
event emitted by the form
element and emits an ngSubmit
event that you can bind to a callback function.
Get access to controls
signupForm.get('username')
signupForm
Nested groups
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
address: new FormGroup({
street: new FormControl(''),
city: new FormControl(''),
state: new FormControl(''),
zip: new FormControl('')
})
});
}
Address
signupForm.get('userData.username')
Generating form controls with FormBuilder (service)
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
profileForm = this.fb.group({
firstName: [''],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
});
constructor(private fb: FormBuilder) { }
}
Dynamic controls using form arrays (FormArray)
FormArray
is an alternative to FormGroup
for managing any number of unnamed controls. As with form group instances, you can dynamically insert and remove controls from form array instances, and the form array instance value and validation status is calculated from its child controls. However, you don't need to define a key for each control by name, so this is a great option if you don't know the number of child values in advance.
Use the FormBuilder.array()
method to define the array, and the FormBuilder.control()
method to populate the array with an initial control.
import { FormArray } from '@angular/forms';
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
aliases: this.fb.array([
this.fb.control('')
])
});
Accessing the FormArray control
A getter provides easy access to the aliases in the form array instance compared to repeating the profileForm.get()
method to get each instance. The form array instance represents an undefined number of controls in an array. It's convenient to access a control through a getter, and this approach is easy to repeat for additional controls.
get aliases() {
return this.profileForm.get('aliases') as FormArray;
}
Define a method to dynamically insert an alias control into the alias's form array. The FormArray.push()
method inserts the control as a new item in the array.
addAlias() {
this.aliases.push(this.fb.control(''));
}
Displaying the form array in the template
Aliases
Another example
'hobbies': new FormArray([]);
onAddHobby(){
const control = new FormControl(null,Validators.required);
//expilicit cast
(this.signupForm.get('hobbies')).push(control);
}
getControls(){
return (this.signupForm.get('hobbies')).controls;
}
getControls()
Add validation
in ts code
import {FromControl,FormGroup, Validators} from '@angular/forms';
@Component()
export class AppComponent implements OnInit {
// declare type
signupForm: FormGroup;
ngOnInit(){
this.signupFrom = new FormGroup({
'username':new FromControl(null, Validators.required),
'email': new FormControl(null, [Validators.required, Validators.email]),
'gender': new FormControl('male')
});
}
}
create custom validators example
//key value pair
forbiddenNames(control: FormControl): {[s: string]:boolean} {
if(this.forbiddenUsernames.indexOf(control.value) !== -1){
return {'nameIsForbidden':true};
}
// if validation is successful, have to return null
return null;
}
when use as validator
this.forbiddenNames.bind(this)
Async validators
//key value pair
forbiddenEmails(control: FormControl): Promise | Observable {
const promise = new Promise((resolve,reject) => {
if
resolve({'emailIsForbidden': true});
else
resolve(null);
}
return promise;
}
React to value changes and status changes
two observables
this.signupForm.valueChanges.subscribe(
(value) =>
);
this.signupForm.statusChanges.subscribe(
(status) =>
);
RxJS
RxJS is a library for composing asynchronous and event-based programs by using observable sequences.
- Observable: represents the idea of an invokable collection of future values or events.
- Observer: is a collection of callbacks that knows how to listen to values delivered by the Observable.
- Subscription: represents the execution of an Observable, is primarily useful for cancelling the execution.
- Operators: are pure functions that enable a functional programming style of dealing with collections with operations like
map
, filter
, concat
, reduce
, etc.
- Subject: is the equivalent to an EventEmitter, and the only way of multicasting a value or event to multiple Observers.
- Schedulers: are centralized dispatchers to control concurrency, allowing us to coordinate when computation happens on e.g.
setTimeout
or requestAnimationFrame
or others.
- Subscriber: Implements the
Observer
interface and extends the Subscription
class. While the Observer
is the public API for consuming the values of an Observable
, all Observers get converted to a Subscriber, in order to provide Subscription-like capabilities such as unsubscribe
. Subscriber is a common type in RxJS, and crucial for implementing operators, but it is rarely used as a public API.
Observables
- Observables provide support for passing messages between publishers and subscribers in your application.
- Observables are declarative—that is, you define a function for publishing values, but it is not executed until a consumer subscribes to it.
- The subscribed consumer then receives notifications until the function completes, or until they unsubscribe.
Usage
As a publisher, you create an Observable
instance that defines a subscriber function. This is the function that is executed when a consumer calls the subscribe()
method. The subscriber function defines how to obtain or generate values or messages to be published.
To execute the observable you have created and begin receiving notifications, you call its subscribe()
method, passing an observer (This is a JavaScript object that defines the handlers for the notifications you receive). The subscribe()
call returns a Subscription
object that has an unsubscribe()
method, which you call to stop receiving notifications.
observable.subscribe(observer)
Invoke
next(), <-------------------------|
error(), <-----------------------|
complete() <-------------------|
observer -----------------> observable ----------------------> Http Request
Subscription Wraps Callback
three notification types called on the observer side: next(),error(),complete()
- observer is what we basically pass into subscribe. tell observer the new data, error, completion. (a listener)
- observable is a wrap around the stream of values, use subscribe to be informed about change in data. observable emitting data and listening to that data in different palces of our application. we can subscribe to certain updates, changes and push these changes from a different place. observable is like "passive" subject can actively trigger.
angular obervables like(params) do not need to unsubscribe.
Defining observers
A handler for receiving observable notifications implements the Observer
interface. It is an object that defines callback methods to handle the three types of notifications that an observable can send:
next
Required. A handler for each delivered value. Called zero or more times after execution starts.
error
Optional. A handler for an error notification. An error halts execution of the observable instance.
complete
Optional. A handler for the execution-complete notification. Delayed values can continue to be delivered to the next handler after execution is complete.
An observer object can define any combination of these handlers. If you don't supply a handler for a notification type, the observer ignores notifications of that type.
Custom observable
import { Subscription, Observable } from 'rxjs';
@Component()
export class HomeComponent implements OnInit {
private firstObsSubscription: Subscription;
constructor() {}
ngOnInit(){
//observer
//next, error, complete
const customIntervalObservable = Observable.create((observer)=>{
let count = 0;
setInterval(handler:()=>{
// call next to emit a new value
observer.next(count);
if(count == 2)
observer.complete();
if(count>3){
observer.error(new Error());
}
count++;
}, timeout:1000);
});
// start using observerables
this.firstObsSubscription = customIntervalObservable.subscribe(data => {
console.log(data);
},error => {
console.log(error);
},()=>{
console.log('completed');
});
}
ngOnDestroy(){
this.firstObsSubscription.unsubscribe();
}
}
Operators
map()
chain of operators
import { map } from 'rxjs/operators';
customIntervalObservable.pipe(filter(),map(data => {
return ;
)).subscribe();
Subject
like an observable, but more actively need to be triggered ( like event emitter). note observable, next() can only be called inside when created.
in service
import {Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({providedIn: 'root'})
export class UserService {
activatedEmitter = new Subject();
}
To emit
this.userService.activatedEmitter.next(true);
when use, use as an observable.
- In this case subject is a suitable replacement for angular event emitter. (use as cross component event emitters, when maually call next(emit), through services).
- when using @output, not suitable for using Subject
Service and dependency injection
Service is a broad category encompassing any value, function, or feature that an app needs. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.
A component can delegate certain tasks to services, such as fetching data from the server, validating user input, or logging directly to the console. By defining such processing tasks in an injectable service class, you make those tasks available to any component. You can also make your app more adaptable by injecting different providers of the same kind of service, as appropriate in different circumstances.
dependency injection
Components consume services; that is, you can inject a service into a component, giving the component access to that service class. To define a class as a service in Angular, use the @Injectable()
decorator to provide the metadata that allows Angular to inject it into a component or other class (such as another service, a pipe, or an NgModule) as a dependency.
-
The injector is the main mechanism. Angular creates an application-wide injector for you during the bootstrap process, and additional injectors as needed. You don't have to create injectors.
-
An injector creates dependencies, and maintains a container of dependency instances that it reuses if possible.
-
A provider is an object that tells an injector how to obtain or create a dependency.
For any dependency that you need in your app, you must register a provider with the app's injector, so that the injector can use the provider to create new instances. For a service, the provider is typically the service class itself.
When Angular creates a new instance of a component class, it determines which services or other dependencies that component needs by looking at the constructor parameter types. For example, the constructor of HeroListComponent
needs HeroService
.
constructor(private service: HeroService) { }
When Angular discovers that a component depends on a service, it first checks if the injector has any existing instances of that service. If a requested service instance doesn't yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular.
When all requested services have been resolved and returned, Angular can call the component's constructor with those services as arguments.
Providing services
You must register at least one provider of any service you are going to use. The provider can be part of the service's own metadata, making that service available everywhere, or you can register providers with specific modules or components. You register providers in the metadata of the service (in the @Injectable()
decorator), or in the @NgModule()
or @Component()
metadata
-
By default, the Angular CLI command ng generate service
registers a provider with the root injector for your service by including provider metadata in the @Injectable()
decorator. The tutorial uses this method to register the provider of HeroService class definition. (something can be injected in there)
@Injectable({
providedIn: 'root',
})
When you provide the service at the root level, Angular creates a single, shared instance of HeroService
and injects it into any class that asks for it. Registering the provider in the @Injectable()
metadata also allows Angular to optimize an app by removing the service from the compiled app if it isn't used.
-
When you register a provider with a specific NgModule, the same instance of a service is available to all components in that NgModule. To register at this level, use the providers
property of the @NgModule()
decorator (in app.module.ts)
@NgModule({
providers: [
BackendService,
Logger
],
...
})
-
When you register a provider at the component level, you get a new instance of the service with each new instance of that component. At the component level, register a service provider in the providers
property of the @Component()
metadata.
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ]
})
Inject services into services
by providing services at app.module level
Routing
in app-routing.module.ts
import {Routes,RouterModule} from "@angular/router";
// starting page
// localhost:4200/users, then component(action)
const appRoutes: Routes = [
{ path: '', component:HomeComponent }
{ path: 'users', component:UsersComponent }
{ path: 'servers', component:ServersComponent }
];
// register routes in NgModule
imports:[
RouterModule.forRoot(appRoutes)
],
exports:[RouterModule]
in app.module.ts
imports:[
AppRoutingModule
]
in app.component.html
The Angular Router
enables navigation from one view to the next as users perform application tasks.
client side routing: readiung urls and rerendering parts of the page.
server side routing: handling incoming requests and sending back sth different.
nested routes
const appRoutes: Routes = [
{ path: '', component:HomeComponent }
{ path: 'users', component:UsersComponent }
{ path: 'servers', component:ServersComponent, children: [
{path: ':id', component:ServerComponent}
{path: ':id/edit', component: EditServersComponent}
] }
];
navigate with router-links
routerLink = '/servers'
[routerLink] = "['/users']"
[routerLink] = "['/users',5,'edit']"
/server
server
./server
../server
styling active router-links
routerLinkActive="active"
[routerLinkActiveOptions] = "{exact: true}"
navigate programmatically
The route path and parameters are available through an injected router service called the ActivatedRoute.
import { Router, ActivatedRoute } from '@angular/router';
// ActivatedRoute: currently active route
constructor(private router: Router, private route: ActivatedRoute) {}
this.router.navigate(['/servers']);
// give relative path
this.router.navigate(['servers'], {relativeTo: this.route});
pass parameters to routes and fetch
'users/:id'
constructor(private route: ActivatedRoute) {}
this.route.snapshot.params['id']
fetch route parameters reactively
// params is an observable, work with async task
// this is useful when params changes
this.route.params
.subscribe(
(params: Params)=>{
this.user.id = +params['id'];
this.user.name = params['name'];
}
)
Query params and fragments
// query params
?mode=editing&active=true
//fragments
#loading
[queryParams] = "{mode: 'editing', active: 'true'}"
[fragment] = "'loading'"
fragment = "loading"
// pass in programmatic way
this.router.navigate(['/servers',id,'edit'],{queryParams: {allowEdit:'1'}, fragment: 'loading'});
retrieve
import { ActivatedRoute } from '@angular/router';
// ActivatedRoute: currently active route
constructor(private route: ActivatedRoute) {}
this.route.queryParams.subscribe(
(params: Params)=>{
}
);
this.route.fragment.subscribe();
// or use the snapshot
handling query parameters
// preserve or merge(overwrite)
this.router.navigate(['servers'], {relativeTo: this.route, queryParamsHandling: 'preserve'});
Redirect
By default, Angular matches paths by prefix.
To fix this behavior, you need to change the matching strategy to "full"
:
{ path: '', redirectTo: '/somewhere-else', pathMatch: 'full' }
// wildcard, catch all]
// put at last
const appRoutes: Routes = [
{ path: '**', redirectTo:'/not-found' }
];
Guards(canActivate,canActivateChild, canDeactivete)
run before the component loaded
CanActivate,canActivateChild
auth-guard.service.ts
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService){}
// can run either sync or async
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean{
return this.authService.isAuthenticated()
.then(
(authenticated: boolean) => {
if(authenticated) {
return true;
}else{
this.router.navigate(['/']);
return false;
}
)
}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean){
return this.canActivate(route,state);
}
}
in app-routing.module
{path: '', canActivate: [AuthGuard]}
// protect the child route
{path: '', canActivateChild: [AuthGuard]}
canDeactivete
control navigation
can-deactivate-guard.service.
// force the class to provide some logic
export interface CanComponentDeactivate {
canDeactivate: () => Observable | Promise | boolean;
}
export class CanDeactivatedGuard implements CanDeactivate {
canDeactivate(component: CanComponentDeactivate, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot,
nextState? : RouterStateSnapshot): Observable | Promise | boolean{
return component.canDeactivate():
}
in app-routing.module
{path: '', component: , canDeactivate: [CanDeactivateGuard]}
in component
// implement the actual logic
canDeactivate() => Observable | Promise | boolean{
}
pass static data to route
{path: '', component: , data: {message: ''}}
use
constructor(private route: ActivatedRoute){}
ngOnInit(){
//this.errorMessage = this.route.snapshot.data['message'];
//possibly change when we are still on the page
this.route.data.subscribe(
(data: Data) ={
this.errorMessage = data['message'];
}
}
dynamic data (Resolver)
resolver: allow run some code before the router is rendered. does not decide the router should be rendered or not. whether the component should be loaded or not. it always render the component in the end but do some pre-loading.
interface Server {
id: number;
name: string;
status: string;
}
export class ServerResolver implements Resolve
constructor(private serversService: ){}
resolve(route: AcrivatedRouteSnapshot, state: RouterStateSnapshot): Obervable | Promise | Server {
return
}
in app-routing.module
{path: '', component: , resolve: { server: NameResolver}},
this.route.data.subscribe((data: Data) => {
this.server = data['server'];
}
Pipes
transform values
parametrizing pipes
xxx | date: 'fullDate'
chain pipes
| | |
create a custom pipe
can use ng generate pipe name
create a new file xxx.pipe.ts
update arrays or objects doesn't trigger the pipe
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: 'shorten',
// pipe recalculate when updated. default set to true.
pure: false
})
export class ShortenPipe implements PipeTransform{
transform(value: any, limit: number){
return value.substr(0,limit);
}
}
in app.module
declarations: [
ShortenPipe
]
in html
xxx | shorten
//pass parameter
xxx | shorten: 5
Async pipe
recognize promise, observable and output when async task finish
(XXX | async)
HTTP
in app.module.ts
import { HttpClientModule } from '@angular/common/http';
imports[HttpClientModule]
in app.component.ts
import {HttpClient} from '@angular/common/http';
constructor(private http: HttpClient) {}
Send a POST Request
// http client take JS object and convert to JSON
// post returns an observable
this.http.post('url', postData).subscribe(responseData => {
console.log(responseData);
});
GET data
this.http.get('url').subscribe(posts => {
});
DELETE data
// observable, can return it
return this.http.delete('url');
transform response data
use rxjs operator
import {map} from "rxjs/operators";
this.http
.get('url')
.pipe(map(responseData => {
return posts
}))
.subscribe(posts => {
});
use types with httpclient
// store response body type
// avaliable on all requests
.get<{[key:string]: Post}>('url')
where Post is the model defined in post.model.ts
loading indicator
create a variable isLoading and set to true when start fetching, and set to false when done (in subscribe funtion)
Use service for http Requests
- If the component doesn't care about the response whether the request is done or not, there is no reason to subscribe in the component, just subscribe in the service.
- if it does care about the response and the response status as it does for fetching posts, then in the service, return the observable, subscribe in the component.
Handling errors
//in subscribe
.subscribe(data=>{}, error => {});
use Subject for error handling
import { Subject } from 'rxjs';
error = new Subject();
.subscribe(xxxxx, error => {
this.error.next(error.message);
});
user catchError Operator
.catchError(errorRes => {
return throwError(errorRes);
}
Set Headers, add query params
import {HttpHeaders, HttpParams} from '@angular/common/http';
.get('url',
{
headers: new HttpHeaders({key: value}),
params: new HttpParams().set('property','value')
})
// or multiple params
let serachParams = new HttpParams();
serachParams = searchParams.append('print','pretty');
searchParams = searchParams.append('custom','key');
Observe different types of responses
full response(body,headers,...)
.post<{name: string}>(
'url',
postData,
{
observe: 'response'
}
)
control the request status
import { tap } from 'rxjs/operators';
// execute some code without ordering the response
return this.http.delete( 'url',
{
observe: 'events'
}
).pipe(tap(event => {
event.type === HttpEventType.Sent
event.type === HttpEventType.Response
}));
Change the response body type
.post<{name: string}>(
'url',
postData,
{
responseType: 'json'
}
)
Interceptors
simplify the requests ( process the requests like middleware in nodejs)
create interceptors: e.g auth-interceptor-service.ts
import { HttpInterceptor, HttpRequest } from '@angular/common/http';
export class AuthInterceptorService implements HttpInterceptor {
intercept(req: HttpRequest, next: HttpHandler) {
//manipulate request object
const modifiedRequest = req.clone({headers: req.headers.append('xx','yy')});
// return an observable
// manipulate the response
// let the request continue
return next.handle(modifiedRequest).pipe();
}
}
in app.module.ts
providers: [{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true}]
Authentication
Dynamic Components
Use *ngIf whenever possible.
Modules
modules generally don't communicate with each other, unless export it to another module.
Lazy Loading
initially only load the root route, then only when visit the other modules, we load the module and the components belong to that module.
NgRx
state management in bigger Angular applications (replacement for services and subjects)
application state
So basically, any data, any information that controls what should be visible on the screen, that is state.
npm install --save @ngrx/store
An example of using NgRx to Add Ingredient to the shoppinglist.
define the action and reducer
in store folder
shopping-list.reducer.ts
Now that reducer as you learned here is just a function and now the important thing about that reducer function is that NgRx will automatically pass in the current state and an action it received, so it will execute this function whenever a new action is received and then this function executes and gets both the current state and the action that was received. In the reducer, I then use a switch case statement and this is a typical setup you see in the Redux and NgRx world, where we check the type of action we got because action is actually a Javascript object with a type property and depending on the type which we have, we return a new state and that is how a reducer function always works, data in data out, no asynchronous code, it's all synchronous, so we only have synchronous code in here and we always return a new object which will be used to replace the old state for this part of the application, so for the shopping list here and this return state is what NgRx will in the end register for the shopping list slice of the overall AppState, of the App store it manages here.
import { Ingredient } from '../../shared/ingredient.model';
import * as ShoppingListActions from './shopping-list.actions';
const initialState = {
ingredients: [
new Ingredient('Apple', 5),
new Ingredient('kiwi', 10)
]
};
// set default value to function argument
export function shoppingListReducer(
state = initialState,
action: ShoppingListActions.AddIngredient
) {
switch (action.type) {
case ShoppingListActions.ADD_INGREDIENT:
return {
...state,
ingredients: [...state.ingredients, action.payload]
};
// for the initial state
default:
return state;
}
}
shopping-list.actions.ts
we also added an actions file where we for one defined unique identifiers for all our actions, these are simply strings that identify each action and then the action itself is not just this identifier but it's a complete object based on classes we define in here. Each action needs to have a type property where we do store the string identifier for the action but in addition, we might also have a payload, so a property which can be set to attach data to that action and we needed
import {Action} from '@ngrx/store';
import { Ingredient } from '../../shared/ingredient.model';
export const ADD_INGREDIENT = 'ADD_INGREDIENT';
export class AddIngredient implements Action {
readonly type = ADD_INGREDIENT;
payload: Ingredient;
}
// typescript feature
// export the type
// multiple actions
export type ShoppingListActions = AddIngredient | AddIngredients;
in app.module
So we added NgRx to our application by including the store module and calling for root. For root then needs a map, so basically an object that tells NgRx which reducers we have in our application because all these reducers and the state they generate make up the store of this application, make up the NgRx store. Now we add a feature for that with an identifier of our choice and then the reducer for that feature.
imports: [
// structure of the store
StoreModule.forRoot({shoppingList: shoppingListReducer}),
]
use in shopping-list.component (to select state)
@Component({
})
export class ShoppingListComponent implements OnInit, OnDestroy {
ingredients: Observable<{ingredients: Ingredient[]}>;
private subscription: Subscription;
// injectable
// key name should match the one defined in app.module
// the type of data stored in shoppinglist is what the reducer function yields
constructor(
private slService: ShoppingListService,
private store: Store<{ shoppingList: { ingredients: Ingredient[] } }>
) { }
// the purpose is to get access to the ingredient stored in the store
ngOnInit() {
// return an observable
// select the shopping list part of the global store
this.ingredients = this.store.select('shoppingList');
}
onEditItem(index: number) {
this.slService.startedEditing.next(index);
}
ngOnDestroy() {
}
}
in shopping-edit component
onSubmit(form: NgForm) {
const value = form.value;
const newIngredient = new Ingredient(value.name, value.amount);
if (this.editMode) {
this.slService.updateIngredient(this.editedItemIndex, newIngredient);
} else {
// this.slService.addIngredient(newIngredient);
// dispatch the new actions to our store
this.store.dispatch(new ShoppingListActions.AddIngredient(newIngredient));
}
this.editMode = false;
form.reset();
}
The flow of this add ingredient process
if we add an ingredient, we dispatch this action which is defined in the actions file to that store, to the @ngrx/store, we only have one such store in the entire application, setup in the app-module and that store is aware of the shoppingListReducer because we have to create such a store with all the reducers that matter. So then the action automatically reaches all the reducers that our store knows, so in this case this one reducer here and in that reducer, the action is therefore passed in as a second argument and now we can check the different types of actions and react appropriately.
Effects
npm install --save @ngrx/effects
aysnc code and side effects
Router Store
npm install --save @ngrx/router-store
in app.module
StoreRouterConnectingModule.forRoot(),
give you a quick and easy way of reacting to different routing events and different data attached to these events.