Angular Architecture/Best Practices Flashcards
software requirements
Node js, Angular CLI, VS code
Architecture Considerations
APP OVERVIEW - what is the application for, what are the goals, how is the client going to use it, what are the business and strategic benefits
APP FEATURES
DOMAIN SECURITY - are we using roles on the server side, groups, claims; how are they going to be communicated to angular app; how is angular going to communicate with our api, is it going to use token based or ldap with active directory
DOMAIN RULES - are they going to run on client side or server side. we need to run on both sides is good for validations. there were might be some number crunching ones, which might need api call and run on the server
LOGGING: when error in front end application
api or integrate with cloud
it helps in bug support and also have good statistics about the app
SERVICE/COMMUNICATION: how angular app is going to talk to the server
normally it is http/https
if your app might have real time data considerations then web sockets might be
not just thinking about data exchange between front end and back end but thinking about between service and components is helpful
DATA MODEL: api to angular; what are we passing to components
are we getting what we want from the api. in many cases data models with api give lot of data, we might only need subset of the data;
we might need to think if we are going to use viewmodel, models only the component needs
FEATURE COMPONENTS: what are our feature components and how are we going to structure our feature components
component commucation -
we might need to know key features upfront
SHARED FUNCTIONALITY- any 3 rd party components that we are going to use
are we going to use them directly or use a wrapper
shared in just one app or use across app; then we think about libraries
Architecture planning Template
https://docs.google.com/presentation/d
/1jrkbbB3eRtTOrbkaljQxLuypHsdUHDl3_
4uFFnHTSNo/edit#slide=id.p4
https: //codewithdan.me/angular-architecture-planning
https: //codewithdan.me/angular-architecture-planning-example
Domain Security
using tokens
using HttpInterceptor could be used to set auth header
what is the back end that issues token, and refresh tokens
Domain Rules
validation
Route guard for not to access without login
validate credentials
Logging
Azure app insights angular service for logging Handle errors based on different environment dev/stage/prod i used ngx-logger nlog - graylog
Services/communication
restful services -on back for this project node.js is being used. it could be java asp.net core, php Angular services below: Data service : use http client ; for customer/orders and login/logout(auth) Sorting Service Filtering Service Logger Service Mediator/bus if needed Component to component communication
HttpInterceptor will set token for HTTP requests if token GET/PUT/POST/DELETE/PATCH - to set header
Use PATCH - to update specific properties - one or two properties
Data Models
Application Models/Interface
Create class or just use interface on the client side
if we use interface, there is zero impact on the bundle size for production
it is better to opt for interfaces unless the classes are more specialized; like if model has properties and models
if you are just using the data from the server or using the object to scale to a smaller object then interfaces are just fine on client side
Interface is great for intellisense or code help
How to reduce the bundle size
How to reduce the bundle size
Feature Components
decide what are angular components needed for features of the app
Shared Functionality
Toast Modal Dialog Google map pager Menu Grid/cards
are these just feature used once or used in multiple feature components
this decides where to pull these in the folder hierrachy
if they are going to used by other applications then we could have a shared library
any 3 rd party components like prime Ng Ng Bootstrap Angular Material ag-grid
Take time for architecture it helps
you will be so much ahead in planning the application architecture
The folder layout
the reuse of code
we might change, as we are still in the initial phase
angular style guide
https://angular.io/guide/styleguide coding conventions naming rules application sturcture organizing modules creating and using components creating and using services life cycle hooks
Other considerations
Accessibilty - may be design consideration
i18n - Internationalization
Environments-
CI/CD
CDN/Container/Server - how are we deploying code to production
course on containerizing angular applications with docker
Unit testing- built in cli tools, karma test runner
End to end testing - protractor or Cypress
this might impact your design; the testers might prefer to have id on all the tags to make it easy to find
API’s - backend where the security happens; data validation
Performance of the application
Documentation
Organizing Features
Do structure the app such that you can LOCATE code quickly, IDENTIFY the code at glance, keep the FLATTEST structure you can, and TRY to be DRY (Do not repeat yourself)
LIFT
LOCATE CODE QUICKLY-
easy to structure the folder structure; once we get bundles it does not matter
IDENTIFY THE CODE AT A GLANCE- how we name our files like feature.component.ts data.service.ts canactivate.guard.ts = for route guards
KEEP THE FLATTEST STRUCTURE YOU CAN-
do not keep nested folders
Try to be DRY:
like we could use services to reuse
we could reuse directives and components
Organizing files - convention based - may be not recommended if there are multiple features
like putting all the components in one folder
putting all the services in one folder
like MVC pattern
Follow strict naming conventions
Related code may be separated
can result in a lot of files in a folder in larger applications
views would be different places
Organizing files - Feature based - Recommended and CLI does by default
May be you could have Feature folder like Customers
and have many components like edit,grid,cards
that are part of the feature
if there are too many then you could have subfolder called components like feature- Customer - Components - put all the components related
it has one extra layer in the es2015 import statement
Features are organized into their own folders
Features are self-contained
Easy to find everything related to a feature
Organizing files - Feature based -how to implement
use angular cli to generate initial feature folder/component ; you could even flatten without creating sub folders
Minimum of one Module per feature (as appropriate) - it helps in lazy loading ; it helps in features to be self-contained; it could be imported at the root module or some other feature if needed ;it is good for maintainence
Avoid Deeply nested folders
Flatten Feature hierrachies - application might have hierrachies like Course-exercise-lab but your code should have exercise folder, course folder, items folder instead of having them in hierarchy
Flatten out top level features as much as possible; this will reduce the need to add ../,../ in the code
if you have nested features that is okey, but be cautious about how deeply you want to nest
Flattening makes - imports to be easier, refactor to be easier, easy to find components
Feature Modules
let us assume we have customer feature and orders feature
ng g c customer = it creates customer component with a folder created at the top level
people normally add this feature in the root module; there is nothing wrong in it as the app grows and grows and gets more feature;
we have one module, we can not use lazy loading and the features are no longer self-contained
usually we could have one module per feature but often instructor has 2
Feature has routing; so we have feature routing module defined with in a module in the feature itself customers-routing.module.ts that would be imported into module for this feature customer.module.ts
if you have complex subfeatures, you would want them to be self-contained, if the portion of the team does it. root module does not need to worry about it
so we are going to have feature-routing module and feature module
Angular Style Guide-Single responsibility
Apply the single responsibility principle (SRP) to all components, services, and other symbols. This helps make the app cleaner, easier to read and maintain, and more testable.
Do define one thing, such as a service or component, per file.
Consider limiting files to 400 lines of code.
Why? One component per file makes it far easier to read, maintain, and avoid collisions with teams in source control.
Why? One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.
Why? A single component can be the default export for its file which facilitates lazy loading with the router.
The key is to make the code more reusable, easier to read, and less mistake prone.
ASG - Small functions
Do define small functions
Consider limiting to no more than 75 lines.
Why? Small functions are easier to test, especially when they do one thing and serve one purpose.
Why? Small functions promote reuse.
Why? Small functions are easier to read.
Why? Small functions are easier to maintain.
Why? Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.
ASG -Naming
Do follow a pattern that describes the symbol’s feature then its type. The recommended pattern is feature.type.ts.
Naming conventions are hugely important to maintainability and readability. This guide recommends naming conventions for the file name and the symbol name.
General Naming Guidelines
Style 02-01
Do use consistent names for all symbols.
Do follow a pattern that describes the symbol’s feature then its type. The recommended pattern is feature.type.ts.
Why? Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.
Why? The naming conventions should simply help find desired code faster and make it easier to understand.
Why? Names of folders and files should clearly convey their intent. For example, app/heroes/hero-list.component.ts may contain a component that manages a list of heroes.
ASG-Separate file names with dots and dashes
Do use dashes to separate words in the descriptive name.
Do use dots to separate the descriptive name from the type.
Do use consistent type names for all components following a pattern that describes the component’s feature then its type. A recommended pattern is feature.type.ts.
Do use conventional type names including .service, .component, .pipe, .module, and .directive. Invent additional type names if you must but take care not to create too many.
Why? Type names provide a consistent way to quickly identify what is in the file.
Why? Type names make it easy to find a specific file type using an editor or IDE’s fuzzy search techniques.
Why? Unabbreviated type names such as .service are descriptive and unambiguous. Abbreviations such as .srv, .svc, and .serv can be confusing.
Why? Type names provide pattern matching for any automated tasks.
Self-contained Features - do not put everything in app.module
with module, all the components and the routing module
self contained feature let different members of a team work on given feature and own it . they are incharge of routing, any services
we just need to import the customer/feature module into the root module i.e, app.module.ts
we need not need to know any details about the feature
exception is if we are lazy loading, we need to define the route and own the root module
any child route or submodules in the customer module, we need not know anything about it. we could just import them
Lazy loading
unless you use lazy loading; all the modules are eagerly loaded that means all the modules and their associated components, directives, pipes and services must be downloaded from the server when the user first visits the application
This takes lot of time
Eager loading
eagerly loaded that means all the modules and their associated components, directives, pipes and services must be downloaded from the server when the user first visits the application
Asychronous routing
https://csharp-video-tutorials.blogspot.com/2018/12/lazy-loading-in-angular.html
loads the feature modules lazily on demand . This can significantly reduce the initial load time of your application
We want to lazily load EmployeeModule. To lazy load a module, it has to meet 2 requirements.
All the routes in the angular module that you want to lazy load should have the same route prefix
const appRoutes: Routes = [
{
path: ‘employees’,
children: [
{ path: ‘’, component: ListEmployeesComponent },
{ path: ‘create’, component: CreateEmployeeComponent },
{ path: ‘edit/:id’, component: CreateEmployeeComponent },
]
}
];
The module should not be referenced in any other module. If it is referenced, the module loader will eagerly load it instead of lazily loading it. Our application already meets the first requirement. All our EmployeeModule routes have the same route prefix i.e employees.
we are removing the employee module reference in app.module.ts we remove it in the imports on the top and imports section as well
EmployeeModule that we want to lazy load is referenced in the AppModule. So please delete the EmployeeModule references in the AppModule (app.module.ts)
we could check all the references by rightclicking on the module name and find all references
Eager loading - in the network tab; there is lot of data loaded and the number of request are more
Eager loading - in the network tab; there is lot of data loaded and the number of request are more
use loadChildren property for lazy loading
in app.module.ts
{path:’employees’,loadchildren:’./employee/
employee.module#EmployeeModule’}
after hash it is the Employee Module class name
in Employee Routing module:
const appRoutes: Routes = [
{
path: ‘employees’,
children: [
{ path: ‘’, component: ListEmployeesComponent },
{ path: ‘create’, component: CreateEmployeeComponent },
{ path: ‘edit/:id’, component: CreateEmployeeComponent },
]
}
];
so the URL needs employees name twice
localhost:4200/employees/employees
we could remove the path:'employees' in the employee module After you remove the 'employees' parent route, the routes should look as shown below.
const appRoutes: Routes = [
{ path: ‘’, component: ListEmployeesComponent },
{ path: ‘create’, component: CreateEmployeeComponent },
{ path: ‘edit/:id’, component: CreateEmployeeComponent },
];
with lazy loading
https://www.youtube.com/watch?v
=75XFBIKLPQY
https://csharp-video-tutorials.blogspot.com
/2018/12/lazy-loading-in-angular.html
load time will be lower; transferred size of data
we could see all in network in chrome developer tools
with lazy loading the modules are loaded on demand we could see request for module load when navigated to the route
about feature module
it has routing module - with does not have anything in the path the routing module is imported into about module in the imports - we have RouteModule.forchild(appRoutes)
import all the components in the feature routing module:
import { AboutComponent } from ‘./about.component’;
const routes: Routes = [
{ path: ‘’, component: AboutComponent }
];
@NgModule({ imports: [ RouterModule.forChild(routes) ], exports: [ RouterModule ] }) export class AboutRoutingModule { static components = [ AboutComponent ]; }
we could import the static variale in the feature module:
import { NgModule } from ‘@angular/core’;
import { AboutRoutingModule } from ‘./about-routing.module’;
@NgModule({
imports: [ AboutRoutingModule ],
declarations: [ AboutRoutingModule.components ]
})
export class AboutModule { }
—
it is not loaded into app.module.ts but loaded into app-routing.module.ts with lazy loading
{path:’about’,loadchildren:’app/about/about.module#AboutModule’}
Core and Shared Modules - DRY code - Do not Repeat Yourself code -CORE MODULES
Core Module:
Core folder should contain singleton services shared throughout app
Services that are specific to a feature can go in the feature’s folder. we could create folder called services in the feature folder and this services is imported in the feature module. in this case the services is not used else where or injected else where in the application
example: LogginService; ErrorService - which intercepts the errors in general; DataService-one specific to customers and one specific to orders
we could call it Core or some people call it Common; but what matters is how we are organizing the code and have everyone on the same page
Core and Shared Modules - DRY code - Do not Repeat Yourself code -SHARED MODULES
Shared Modules -
Reusable components, pipes, directives
example- CalendarComponent
AutoCompleteComponent
may be custom grid that could be used through out the app
will this widget be used only in this app or used across apps? if it is so generic and could be used across applications then we should be creating angular library
Core and Shared Modules importing
Shared Modules may be imported multiple times into RootModule, Feature Modules
Core Module - it is imported only once into the root module (app module)
Preventing Reimport of Core
Core should only be imported into the root/app module
export function throwIfAlreadyLoaded (parentModule:any, moduleName:string){ if(parentModule){ throw new Error('${moduleName} has already been loaded. Import core modules in the AppModule only.') } }
–
Core Module:
import {throwIfAlreadyLoaded } from ‘./import.guard’
export class CoreModule{
constructor(@Optional() @SkipSelf() parentModule:CoreModule)
{
throwIfAlreadyLoaded(parentModule,’CoreModule’);
}
}
in constructor we are having dependency injection
@Optional(): A constructor parameter decorator that marks a dependency as optional; it is okey if there is nothing for this to inject
@SkipSelf()- go to the parent injector and see if we can inject a Core Module
A constructor parameter decoarator that tells the DI framework that dependency resolution should start from the parent injector
skipping self and going up to the parent
we hope that there is no parent module; this way we could have core module not imported into parent module
Using the Base Class: CoreModule can derive from EnsureModuleLoadedOnceGuard
export class EnsureModuleLoadedOnceGuard { constructor(targetModule: any) { if (targetModule) { throw new Error('${targetModule.constructor.name} has already been loaded. Import this module in the AppModule only.'); } } } Alternative Aoproach - Base Class An alternative to throwlfAlreadyLoaded is to convert it into a class
---- import { EnsureModuleLoadedOnceGuard } from './ensure-module-loaded-once.guard'; export class CoreModule extends EnsureModuleLoadedOnceGuard { constructor(@Optional() @SkipSelf() parentModule: CoreModule) { super(parentModule); }
if there is parent module that means it is bad, that measns core module is already imported from someother module and we do not want that
using class instead of function if there are other module that needed to be loaded in one particular place the appModule
General folders in Core Folder
Growler(type of toast message), Interceptors,modal, navbar, overlay, Services like auth.service, logger, trackby and other services and strategies for lazy loading
each of the above have their own module
as there is lot going on with them
it helps in self-contained, test them, use them on their own
if it is simple them then import them into core directly instead of creating module for them
core usually has services but any components that are used only once could go in here
Core folder also has components to use in app-component;
Core - is for single usage
//type of toast message //modal dialog to reuse in the whole app Loading
all these components are needed only single time in the app, like singleton. they do not have to be
anything in the core is used one time in the application
people should remember to not use these selectors for the components in multiple places in the application
some people put the components in shared folder
Shared folder - all here could be reused
■ directives ■ filter-textbox
map N pagination N pipes • interfaces.ts • mocks.ts ✓ router.animations.ts • shared.module.ts
map component - used multiple times in the app
all the above could be reused many times
Creating a Custom Library
if one of your shared module, you want to put in on npm or internal npm to be used by other applications
$ ng new my-project //this creates workspace with one project
$ ng generate library my-lib //custom shared library
Creating a Custom Library:
Angular CLI provides library functionality
The tsconfig file will be updated to look for library reference
Build the library before trying to use it in the workspace
library creates a separate project adds into the existing workspace
so we could test the library with out having to publish into npm, just by using our web project
ng build my-lib //to build the library
now we could test the project
tsconfig. json = it adds path s to the dist folder of the project where we going to build; typescript looks at this file when you try to build
angular. json = it has workspace and the two projects that we have created
Publish a custom library
package.json file will be used to publish to npm
npm publish = this publishes to npm but it needs login
if you are publishing to local npm then you might need path
$ ng build my-lib $ cd dist/my-lib $ npm publish
Publishinc a Custom Liorary
Build your library for production
Publish to npm or to an internal npm
https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry
library project
it has component, module and service samples created
Consuming a custom library
we import into the web project app.module.ts in the array
then the src/public_api = of library gets imported; this exposes all the component, services in the library
we could replace it with package name ; like acme-shared
we could use the library directly in the web project with tag
we need to then bulid the acme-shared project
ng build acme-shared –watch and not the root project
then ng serve -o
then we could push npm
we have created dist folder for the library, we could see the package.json in the folder which has the version name and the name of the package that we will be publishing
we need to run publish with npm command in dist - shared folder
when to go with custom project library
if we see that some widgets are being reused in many apps
then we could creaetd libaray instead of creating them in shared folders
then npm install custom shared package
Widget - Shared or Feature
widget - like menu, calendar,auto complete
if a widget is used by many component then we put it in Shared Module
if a widget is used only in a specific feature component then we put it in Feature Module
Feature/Child Componet
this goes into Feature Module either feature or child to a feature
Services
if reusable put it in core module if a particular service is only use by specific feature then it goes into Feature module
Routed components
the path defined to a specific component, it goes in routing module
Routing module
this will be imported into the feature module
Pipes
in shared module
if only a feature uses a pipe then put in feature module
Exporting of Feature Module
Feature module is a stand alone, it is usually imported into Root module and it does not need to know anything. we will not be exporting anything typically from Feature Module
Routing Module
we will importing routing functionality and export that
Core Module
this is going to imported into root module this will not have any exports Exception is that if we put single use components like menu that should be used one time in the app; in this case we need to export that component
Shared Module
it needs exports; if we need to import in any of the modules
Shared library
we could import when needed in feature module or in the shared module
Container - Presentation pattern for components
if there is a big component then we break it down to child components
Container component - which has mutiple presentation component
Container component manages the state (interact with service/store); it is boss/manager
Presentation component - presnts/renders the state
the presentation component do not get the state; they just get what container component gives to them
Container components gets the data; it might possibily render something as well like heading; but it will hand the data down to presentation component for UI
child components - do not know how to get the data or how to interact with store; they are just presentation component
Container - Presentation pattern for components- Child Routes and Components
Tool bar or Tabbed interface customer: /customers/44 Child : /customers/44/edit child:/customers/44/details
const routes: Route = [
path: ‘customers/:id’, component: CustomerComponent, children: [
{ path: ‘edit’, component: CustomerEditComponent },
{ path: ‘details’, component: CustomerDetailsComponent },
]
}
];
do not build components, very big; break them into child components
if child component has lot of data having a separate child route will help to load the data only on demand
working with Container/Presentation components or Parent and Child components
Pass state from container —> presentation using input properties
Communicate from presentation -> container using output properties
Set change detection strategy to OnPush in presentation components
Consider cloning complex types
Use child routes as appropriate
we are using input properties to call the child components
in customer componnet html
<div> </div>
using router for child components
instead of input properties on parent component, we will have router outlet
in the container- presentation pattern
we load all the data at once and show and hide based on the need
we do not have separate routes
in the parent- child pattern
we have routes set up and we are going to load the child data only on demand when the route is reached
we could bookmark exact route with this way
also if we have lot of data then it is better to load them on demand by using routes
Data transfer from component to template
Property binding [] or Interpolation {} = from component code to Template
Event Binding = to get data from Template to Component
Data Transfer from Container to Presentation
Service –> Container Component –> Presentation
component(@Input () )
Data Transfer from Presentation to Container
Service –> Presentation component (Output()) –>Container Component
output() property uses event emitter which emits the data up to the subscriber the container - then it might interact with the service to send that data
challenges with input and output properties
if we have multi layer
Service ->Container –> Presentation –>Presentation
in this case it might be challenging
input properties in usage
in Parent/Container component:
setting property:
export class CustomerComponent implements OnInit {
customer: any;
constructor() {}
ngOnInit(): void { this.customer = { name: 'john', address: { city: 'Newyork', }, }; }
passing the value to customerDetail component from html:
<p>customer works!</p>
>
Customer Detail component:
export class CustomerDetailComponent implements OnInit {
@Input() CustomerDetail: any;
in html:
<p>customer : {{ CustomerDetail.name }}</p>
in appcomponent.html:
output properties in usage
in customer component:
<p>customer works!</p>
customer : {{ CustomerDetail.name }}
child/presentation component should not change the data or state
child/presentation component should not change the data or state
if the user changes at that level it is fine we could use output properties but usually it is the job of the parent/container component
Change Detection Strategy On Push
OnPush causes change detection to run when:
- An input property reference changes
- An output property/EventEmitter or DOM event fires 3. Async pipe receives an event
- Change detection is manually invoked via ChangeDetectorRef
if we push data in or out then we want the UI to update
this protects us from accidentally updating the state and updating the UI
OnPush Benefits:
OnPush Benefits:
- Performance (component isn’t checked until OnPush conditions are met)
- Prevent the presentation component from updating state it should get from the container/parent
it is better to have onPush on all the presentation components or child components
When using OnPush detectors, then the framework will check an OnPush component when any of its input properties changes, when it fires an event, or when an observable fires an event Victor Savkin (Angular Team)
sort is an event, then it will fire the onPush change detection strategy; it
ChangeDetectionStrategy.OnPush,
@Component({
selector: ‘app-customer-detail’,
templateUrl: ‘./customer-detail.component.html’,
styleUrls: [’./customer-detail.component.scss’],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerDetailComponent implements OnInit {
@Input() CustomerDetail: any;
@Output() CustomerNameChange = new EventEmitter();
constructor() {}
ngOnInit(): void {
this.CustomerDetail.name = ‘peter in onit’;
this.CustomerDetail.name = ‘peter in onit new’;
setTimeout(() => {
this.CustomerDetail.name = ‘peter in timeout’;
}, 2000);
}
we could initialize values in onInit lifecycle but we are setting a delay to simulate the change later on inthe code which does is not allowed
Pass by Value and Pass by Reference
Pass by Value = change detected
Pass by Reference = changes are not detected as the object by itself is still in the same memory location
value type = primitive types like number, string, boolean, null, undefined, symbol
if our container component is updating a value type then the change detection mechanism would catch that and update the child UI
in service: addCustomer() : Observable { let id = this.customers[this.customers.length - 1].id + 1; this.customers.push({ id: id, name: 'New Customer ' + id, city: 'Somewhere', age: id * 5 }); this.customersSubject$.next(this.customers); return of(this.customers); }
we are adding element to the array but the array itself did not change so change detection does not work
export class CustomersListComponent implements OnInit { @Input() customers: Customer[]; since the input did not change the UI did not change
if pass the object by reference, anything in the object changes, that is not going to be reflected in the presentation component.
if we change a property of the customer object, that too will not be reflected in the UI because the outer object did not change its memory location
Cloning Technique
Cloning is the process of making an exact copy of an object
Cloning Techniques:
Json.Parse() and Json.stringify() – simple
Custom
Immutable.js - library designed for it
immutable approach, we do not change the object. but we always make a fresh copy
ngOnChanges()
Respond when Angular sets or resets data-bound input properties. The method receives a SimpleChanges object of current and previous property values.
Note that this happens very frequently, so any operation you perform here impacts performance significantly. See details in Using change detection hooks in this document.
Called before ngOnInit() and whenever one or more data-bound input properties change.
it detects when a new object is coming in through the input property
Communication Between Container - Presentation and Service
- Container component passes data to presentation component using input property
- User changes data
- Output property emits customer to container
- Container talks with service to update customer object
- Container gets updated version of customer from service
ngOnchanges catch point
if the object in memory has not changed then ngOnchanges is not going to fire
if the object does not change its reference, if just the property of object chagnes, then it is not detected with ngOnChanges
Cloning Technique in action with JSON.parse; it is deep copy
in service
getCustomers():Observatble{ const custs= JSON.parse(JSON.stringify(this.customers)); return of(custs); {
getCustomer(id:number):Observable{
return this.getCustomers() .pipe( map(custs=>{ const fiteredCusts = custs.filter(cust=>cust.id===id); if (filteredCusts) { const cust= fitleredCusts[0]; return JSON.parse(JSON.stringify(cust)) as customer; }
}
)
)
}
with dates it will not work
ngOnChange
it detects when an change is made with the input property
if the service/container/ and both of the presentation has reference to the objects and we updated the properties but we did not change; so ngOnChange is not fired
the object in memory did not change but just the property inside it did
Cloning Technique in action with custom srevice
getCustomers() : Observable { const custs = this.clonerservice.deepClone(this.customers); return of(custs); }
this service uses 3 rd party NPM package called clone
it deals with dates
this is very light weight solution regarding the bundle size when we go to production, this is minimal library
cloner.service.ts = for cloning the object; npm install clone
import { Injectable } from ‘@angular/core’;
import * as clone from ‘clone’;
@Injectable({ providedIn: 'root' }) export class ClonerService {
deepClone(value) { return clone(value); }
}
there are different cloning techniques but here we are using clone
cloning with immutable.js ; when there are lot of things immutable or clonable
impor {List, Map, fromJS} from ‘immutable’;
npm install immutable
this is from facebook
List object to work with arrays
Map object to work with key value pairs
from JS - can do a deep copy and create it to map, we could convert it into a normal javascript object
cloning with immutable.js example
getCustomers() : Observable{ const custs = this.immutableCustomers.toArray(); return of(custs); }
Note that toArray() “shallowly converts” the colelction to an Array. usr toJS() to deeply convert the collection to an array
cloning with immutable.js example for customers
getCustomer(id:number):Observable{
return this.getCustomers() .pipe( map(custs=>{ const fiteredCusts = custs.filter(cust=>cust.id===id); if (filteredCusts) { const cust= fitleredCusts[0]; return fromJS(cust).toJS() as cusomer; }
}
)
)
}
fromJs, converts to javascript object of type any; under the cover it is called map and then it is converted to Javascript object using toJS(); it is a way to clone cast it to customer to return observable
with immuatble js or any cloning strategy;
using ngOnChanges to detect input property changes
in component.ts:
@Input() customer:any;
ngOnChanges(changes: SimpleChanges) { if(changes['customer']) { const cust= changes['customer'].currentvalue as customer; this.addTax(cust); this.logMessage.Push({title:'customer chagned',value:cust}); } }
usually the methods like addTax, is better to handle at the service level instead of component level
@Input() with getter or setter in es2015
we could call the set block in the ngOnChanges() but the reference to the object needs to be changed
changes are usually not shown thinking that the outer object did not change
Component Inheritance - it is not preferrable
save you lot of code and maintainable
the base component will not have template but it will have component code including functions and ofcourse the input and outputs
widget 1 and widget 2 will inherit from it
it is not preferrable but if you feel that there is lot of duplication especially with the input and output properties then it makes more sense
Component Inheritance in action- base component
import { Component, OnInit, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges, SimpleChanges } from ‘@angular/core’;
@Component({
selector: ‘app-base-component’,
template: ‘’,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BaseComponent implements OnInit, OnChanges {
@Input() label: string;
private _value: string; @Input() get value() { return this._value; } set value(val: string) { if (val && val !== this._value) { this.isDirty = true; } this._value = val; this.valueChange.emit(val); }
@Input() isDirty = false; @Output() valueChange = new EventEmitter();
constructor() { }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges) { if (changes['value']) { console.log('Value changed ', changes['value'].currentValue); } }
}
Component Inheritance in action - in child/widget component
import { Component, OnInit } from ‘@angular/core’;
import { BaseComponent } from ‘../base-component/base-component.component’;
@Component({
selector: ‘app-widget1’,
templateUrl: ‘./widget1.component.html’,
styleUrls: [’./widget1.component.css’]
})
export class Widget1Component extends BaseComponent implements OnInit {
constructor() {
super();
}
ngOnInit() {
}
}
in html:
<h3>{{ value }}</h3>
{{ label }}
Cloning when to use
if you are getting fresh data from service from server; then we do not need to clone; as it is always a new object; same thing with post put delete;
we do not need to clone as long as the object chagnes;
in cases where we are calling the server and caching the data for a while, while the user edits or even lookup data; then we need to look for cloning
Structuring components overview
Break complex components into child components
Use the container -3 presentation pattern where possible
Container retrieves state and presentation components render Use OnPush on presentation components Leverage cloning/immutable data when appropriate Use component inheritance sparingly (when it makes sense)
Component Communication challenges
1: if deeply nested components need to communicate
2: if we want to communicate with one or more components at the same time
in the above cases using @Input and @Output might be challenging
we could use event bus or observable service techniques, both use rxjs subjects
this could also be used to communicate between services
Component communication with Event Bus
Event Bus Mediator pattern
Angular service acts as the middleman between components
Components don’t know where data is coming from by default
Loosely coupled Relies on subject/observable
Component communication with Observable Service
Observer pattern
Angular service exposes observable directly to components
Components know where data is coming from
Not as loosely coupled as event bus
Relies on subject/observable
RxJS Subjects
Subject
BehaviorSubject
ReplaySubject
AsyncSubject
http://reactivex.io/documentation/subject.html
RxJS Subjects -Subject
Subjects provide one or more data values to listeners
Listeners subscribe and get the data
if another component subscribes, they are not going to get all the data as the first component
with subject, you are going to get the data only that is emitted after subscribed and do not get the previously emitted data
RxJS Subjects -BehaviorSubject
if a subscriber comes later in the game, they could still get the previous emitted data
the later subscribers could get the last emitted value from before; and going forward will get all ot them
this is powerful when you want to keep components uptodate when they subscribe later
RxJS Subjects -ReplaySubject
BehaviorSubject is a type of replay subject
it replays the last value emitted to any new subscribers
it could also replay all of the previous values if you like
it is more like caching any data that is being sent out; so that any component subscribe still can get the data
new value emitted will be sent to all the components
this is helpful when you want to send stream of data for the late subscribers in the game
we could control how much of the new data to be sent
RxJS Subjects -AsyncSubject
we want only the last value as the subject complete
there might be multiple values sent out but we are only interested in uptodate value
though there are values emitted continuously and component is subscribed, we are only going to receive the last value before the subject completes
used in any case only the last relevant data
Subjects in action
import { Subject, BehaviorSubject, ReplaySubject,
AsyncSubject, Observable } from ‘rxjs’;
export class SubjectService {
customers = [];
intervalIds = [];
private subject$: Subject;
subjectObservable$: Observable;
private behaviorSubject$;
behaviorSubjectObservable$: Observable;
private replaySubject$: ReplaySubject;
replaySubjectObservable$: Observable;
private asyncSubject$: AsyncSubject;
asyncSubjectObservable$: Observable;
—
all the subjects are private and all the observables are public (default in typescript, if you do not specify any)
$ sign in the name, is just a naming convention to say that we are going to have a stream of data
importing subjects; defining subjects which exposes observables
all these subjects are pushing a new item into array of customers
we want the subject to be private, because any component could subscribe to a subject, if we expose they can not only subscribe but do anythign that they want to the subject. we do not want that to happen. Instead from the subject we expose an observable
webpage is echoing the latest
<h2>Status: {{ status }}</h2>
<div class="row"> <div class="col-md-3"> <h4>Subject</h4> <ul> <li>{{ data.length }}</li> </ul> </div> <div class="col-md-3"> <h4>BehaviorSubject</h4> <br> <ul> <li>{{ data.length }}</li> </ul> </div> <div class="col-md-3"> <h4>ReplaySubject</h4> <br> <ul> <li>{{ data.length }}</li> </ul> </div> <div class="col-md-3"> <h4>AsyncSubject</h4> <br> <ul> <li>{{ data.length }}</li> </ul> </div> </div>
initializing subjects
initSubjects() {
this. subject$ = new Subject(); this. subjectObservable$ = this.subject$.asObservable(); this. behaviorSubject$ = new BehaviorSubject(this.customers); this. behaviorSubjectObservable$ = this.behaviorSubject$.asObservable(); this. replaySubject$ = new ReplaySubject(); this. replaySubjectObservable$ = this.replaySubject$.asObservable(); this.asyncSubject$ = new AsyncSubject(); this.asyncSubjectObservable$ = this.asyncSubject$.asObservable(); }
we are not sending subjects directly; but we are saying them as observable
if you subscribe to it, it will look like normal observable subscription
export interface ICustomer{
name:string;
city:string;
}
pushing with subjects
// simulate array getting new data from a data source let intervalId = setInterval(() => { let len = this.customers.length; this.customers.push({ name: 'Customers ' + len, city: 'City ' + len }); let clone: ICustomer[] = JSON.parse(JSON.stringify(this.customers)); this.subject$.next(clone); this.behaviorSubject$.next(clone); this.replaySubject$.next(clone); this.asyncSubject$.next(clone);
if (this.customers.length > 5) { this.asyncSubject$.complete(); } }, 3000); this.intervalIds.push(intervalId); }
we are using setInterval, which runs every 3 seconds
we are pushing a new customer every time
cloning the object of customer array
then push the customer to any subscribers with next; will pass the appropriate data
consuming/subscribing to subjects in component
constructor(private subjectService: SubjectService) { }
//inject subject service
import { Component, OnInit, OnDestroy } from ‘@angular/core’;
import { SubjectService } from ‘../core/services/subject.service’;
import { SubSink } from ‘subsink’;
@Component({ selector: 'app-subjects', templateUrl: './subjects.component.html', styles: [` .status { color: red; }` ] }) export class SubjectsComponent implements OnInit, OnDestroy { status: string; subjectObservableData = []; behaviorSubjectObservableData = []; replaySubjectObservableData = []; asyncSubjectObservableData = []; timeoutIds = []; subsink = new SubSink();
constructor(private subjectService: SubjectService) { }
ngOnInit() { }
start() {
this.subjectService.start();
this.runAction(‘Calling SubjectService start()’, null, null);
this.runAction(‘Subscribing to Subject’, ActionType.subject, 2000);
this.runAction(‘Subscribing to BehaviorSubject (6 seconds after subject)’, ActionType.behaviorSubject, 8000);
this.runAction(‘Subscribing to ReplaySubject (10 seconds after subject)’, ActionType.replaySubject, 13000);
this.runAction(‘Subscribing to AsyncSubject (12 seconds after subject)’, ActionType.asyncSubject, 15000);
}
runAction(actionText: string, actionType: ActionType, delay: number) {
let action: () => void;
switch (actionType) {
case ActionType.subject:
action = () => {
this.subsink.sink = this.subjectService.subjectObservable$.subscribe(custs => {
this.subjectObservableData.push(custs);
})
};
break;
case ActionType.behaviorSubject: action = () => { this.subsink.sink = this.subjectService.behaviorSubjectObservable$.subscribe(custs => { this.behaviorSubjectObservableData.push(custs); }) }; break; case ActionType.replaySubject: action = () => { this.subsink.sink = this.subjectService.replaySubjectObservable$.subscribe(custs => { this.replaySubjectObservableData.push(custs); }) }; break;
case ActionType.asyncSubject: action = () => { this.subsink.sink = this.subjectService.asyncSubjectObservable$.subscribe(custs => { this.asyncSubjectObservableData.push(custs); }) }; break; }
// update status and perform action let timeoutId = setTimeout(() => { this.status = actionText; if (action) { console.log('in') action(); } }, (delay) ? delay : 0); this.timeoutIds.push(timeoutId); }
ngOnDestroy() { this.subsink.unsubscribe(); for (let id of this.timeoutIds) { clearInterval(id); } }
}
enum ActionType { subject, behaviorSubject, replaySubject, asyncSubject }
Showing subject value in the Subject component UI
<h1>Using RxJS Subjects</h1>
All of the functionality for this demo is in core/subject.service.ts and the subjects folder. Click Start to begin.
<br></br><br></br>
Start
<h2>Status: {{ status }}</h2>
<div class="row"> <div class="col-md-3"> <h4>Subject</h4> <ul> <li>{{ data.length }}</li> </ul> </div> <div class="col-md-3"> <h4>BehaviorSubject</h4> <br> <ul> <li>{{ data.length }}</li> </ul> </div> <div class="col-md-3"> <h4>ReplaySubject</h4> <br> <ul> <li>{{ data.length }}</li> </ul> </div> <div class="col-md-3"> <h4>AsyncSubject</h4> <br> <ul> <li>{{ data.length }}</li> </ul> </div> </div>
<br></br>
<strong>Subject</strong>
Only subscribers receive data
<br></br><br></br>
<strong>BehaviorSubject</strong>
Note how this picks up the last value emitted event though it subscribed after the value was sent out. That’s because
BehaviorSubject allows an initial value to be sent to an observer as they subscribe.
<br></br><br></br>
<strong>ReplaySubject</strong>
Note how this stays in sync with everything above even though it subscribes 10 seconds after the subject. That’s because
it’s replaying everything up to that point from a cache it maintains.
<br></br><br></br>
<strong>AsyncSubject</strong>
This only plays the last item before it completes - nothing before that. It “completes” in the data service once the
customers array length is greater than 5.
<br></br><br></br>
Event Bus Service
Event bus is going to allow us to send data between different components, it is type of mediator or middle man
Event bus can send data between multiple components
Follows the mediator pattern
Uses RxJS Subject
Component
Component going to subscribe to an event of the event bus;
we are going have customer selected event
another component is going to raise an event, may be when a customer is selected and send those details to the Event bus. This component does not know who is listening at all
the first component will be able to get the data and use it
using Event bus and emitting data from component
import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy, SimpleChanges } from ‘@angular/core’;
import { Customer } from ‘../../shared/interfaces’;
import { EventBusService, EmitEvent, Events } from ‘../../core/services/event-bus.service’;
@Component({
selector: ‘app-customers-list’,
templateUrl: ‘./customers-list.component.html’,
styleUrls: [ ‘./customers-list.component.css’ ],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CustomersListComponent {
@Input() customers: Customer[];
@Output() customerSelected = new EventEmitter();
logMessages: string[] = [];
constructor(private eventbus: EventBusService) { }
ngOnChanges(simpleChanges: SimpleChanges) {
if (simpleChanges[‘customers’]) {
this.logMessages.push(‘ngOnChanges Fired: Customers changed’);
}
}
selectCustomer(cust: Customer) { // send to parent via output property // note: could use eventbus as well if desired but output property // would be the preferred method for passing data to am immediate parent this.customerSelected.emit(cust); // Send customer to any eventbus listeners listening for the CustomerSelected event this.eventbus.emit(new EmitEvent(Events.CustomerSelected, cust)); }
}
using Event bus and subscribing data from a component that needs data
export class AppComponent implements OnInit { customers: Customer[]; customer: Customer; eventbusSub: Subscription; customersChangedSub: Subscription;
constructor(private eventbus: EventBusService, private dataService: DataService) {}
ngOnInit() { //Example of using an event bus to provide loosely coupled communication (mediator pattern) this.eventbusSub = this.eventbus.on(Events.CustomerSelected, cust => (this.customer = cust));
//Example of using BehaviorSubject to be notified when a service changes this.customersChangedSub = this.dataService.customersChanged$.subscribe(custs => (this.customers = custs)); }
ngOnDestroy() { // AutoUnsubscribe decorator above makes these calls unnecessary // this.eventbusSub.unsubscribe(); // this.customersChangedSub.unsubscribe(); }
Creating an Observable Service
This provides more control over where the data is emitted from and what is being emitted
it is more of publish-Subscribe pattern or observable pattern
we expose observable to anyone that want to subscribe,as the data changes we will emit that by calling next on our subject, that data will flow down to listeners
Observable services can send data to observers/subscribers
Follows the observer pattern
Provides a simple way to keep multiple observers (components, services) up-to-date
Service can use RxJS Subject objects and observables
Creating an Observable Service - example
private customersSubject$ = new BehaviorSubject(this.customers); customersChanged$ = this.customersSubject$.asObservable();
we are exposing the subject as observable; here we are using BehaviorSubject, so all the subscribers will be able to get the last value that was sent to earlier subscribers
on addCustomer()- we are emitting the observable addCustomer() : Observable { let id = this.customers[this.customers.length - 1].id + 1; this.customers.push({ id: id, name: 'New Customer ' + id, city: 'Somewhere', age: id * 5 }); this.customersSubject$.next(this.customers); return of(this.customers); }
Complete example:
import { Injectable } from ‘@angular/core’;
import { Observable, of, BehaviorSubject } from ‘rxjs’;
import { map } from ‘rxjs/operators’;
import { Customer, Product } from ‘../../shared/interfaces’;
import { ClonerService } from ‘./cloner.service’;
import { List } from ‘immutable’;
@Injectable({ providedIn: 'root' }) export class DataService {
customers: Customer[] = [ { id: 1, name: 'John Doe', city: 'Phoenix', age: 42 }, { id: 2, name: 'Jane Doe', city: 'Seattle', age: 30 }, { id: 3, name: 'Michelle Thompson', city: 'Orlando', age: 22 } ];
products: Product[] = [ { id: 1, name: 'Basketball', price: 29.99 }, { id: 2, name: 'XBox', price: 249.99 }, { id: 3, name: 'Nintendo Switch', price: 249.99 }, { id: 4, name: 'Bat', price: 29.99 }, { id: 5, name: 'Glove', price: 29.99 }, { id: 6, name: 'Cell Phone', price: 799.99 }, { id: 7, name: 'Cell Phone Service', price: 49.99 }, { id: 8, name: 'Laptop', price: 999.99 }, { id: 9, name: 'Bluetooth Speaker', price: 69.99 }, { id: 10, name: 'TV', price: 1599.99 } ];
immutableCustomers = List();
immutableProducts = List();
private customersSubject$ = new BehaviorSubject(this.customers); customersChanged$ = this.customersSubject$.asObservable();
constructor(private cloner: ClonerService) { }
getCustomers() : Observable { // Use the following code if using immutable.js // return of(this.immutableCustomers.toJS());
return of(this.customers); }
getProducts() : Observable { // Use this for immutable.js // return of(this.immutableProducts.toJS());
return of(this.products); }
addCustomer() : Observable { let id = this.customers[this.customers.length - 1].id + 1; this.customers.push({ id: id, name: 'New Customer ' + id, city: 'Somewhere', age: id * 5 }); this.customersSubject$.next(this.customers); return of(this.customers); }
addCustomerClone() : Observable { return this.addCustomer().pipe( map(custs => { return this.cloner.deepClone(custs); }) ) }
addCustomerImmutable() : Observable {
let id = this.immutableCustomers[this.immutableCustomers.size - 1].id + 1;
this.immutableCustomers.push({
id: id,
name: ‘New Customer ‘ + id,
city: ‘Somewhere’,
age: id * 5
});
this.customersSubject$.next(this.customers);
return of(this.immutableCustomers.toJS());
}
addProduct(newProduct: Product) { this.products.push({ id: this.products.length, name: newProduct.name, price: +newProduct.price }); return of(this.products); }
}
Using an Observable Service
just subscribing to the data
first inject the service and then subscribe to it
//Example of using BehaviorSubject to be notified when a service changes this.customersChangedSub = this.dataService.customersChanged$.subscribe(custs => (this.customers = custs));
here maintainance is easy as the data chagnes and the emitting of the observable are happening at the same place unlike Bus service
–
app.component.ts
import { Component, OnInit } from ‘@angular/core’;
import { EventBusService, Events } from ‘./core/services/event-bus.service’;
import { Customer } from ‘./shared/interfaces’;
import { Subscription } from ‘rxjs’;
import { DataService } from ‘./core/services/data.service’;
import { AutoUnsubscribe } from ‘ngx-auto-unsubscribe’;
@AutoUnsubscribe() @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { customers: Customer[]; customer: Customer; eventbusSub: Subscription; customersChangedSub: Subscription;
constructor(private eventbus: EventBusService, private dataService: DataService) {}
ngOnInit() { //Example of using an event bus to provide loosely coupled communication (mediator pattern) this.eventbusSub = this.eventbus.on(Events.CustomerSelected, cust => (this.customer = cust));
//Example of using BehaviorSubject to be notified when a service changes this.customersChangedSub = this.dataService.customersChanged$.subscribe(custs => (this.customers = custs)); }
ngOnDestroy() { // AutoUnsubscribe decorator above makes these calls unnecessary // this.eventbusSub.unsubscribe(); // this.customersChangedSub.unsubscribe(); } }
--- in html: <strong class="pull-right circle"> {{ customers.length }} </strong>
Observable Service Pros and Cons
Pros:
Simple to use -subscribe/unsubscribe
Data source is known (simplifies maintenance)
Easy to share data between different layers of an application
Cons:
Not as loosely coupled as an event bus (coupling between observable and observer)
Subject variations can be challenging to master
Must remember to unsubscribe
unsubscribe from observable
ngOnDestroy = unsubscirbe
Unsubscribing in ngOnDestroy
export class MyComponent implements OnInit, OnDestroy ( eventbusSub: Subscription; constructor(private eventbus: EventBusService) { ngOnInit() f this.eventbusSub = this.eventbus.on(Events.CustomerSelected, (cust => this.customer = cust)); } } ngOnDestroy() { if (this.eventbusSub) { this.eventbusSub.unsubscribe(); }
Each observable subscription must be assigned to a property
Subscribe from observables in ngOnDestroy
if a component has several subscriptions, this could be cumbersome
Unsubscribing using @AutoUnsubscribe() decorator
it is an npm package
it automatically iterates through all the properties, finds all the subscription types and when ngOnDestroy is called, it destroys all subscriptions
we need to have subscription properties and empty ngOnDestroy(){}
@AutoUnsubscribe() export class MyComponent implements OnInit, OnDestroy { eventbusSub: Subscription; constructor(private eventbus: EventBusService, private dataService: DataService) { } ngOnInit() { this.eventbusSub = this.eventbus.on(Events.CustomerSelected, (cust => this.customer = cust)); } ngOnDestroy() { // No need to manually unsubscribe } } Must add ngOnDestroy() Usinc the AutoUnsubscribe Decorator Component subscriptions can automatically be unsubscribed https://github.comiNetanelBasalingx-auto-unsubscribe
using subSink npm for unsubscribing
export class MyComponent implements OnInit, OnDestroy { subs = new SubSink(); constructor(private eventbus: EventBusService, private obsService: ObsService) { }
ngOnInit()
{
this.subs.sink = this.eventbus.on(Events.CustomerSelected, (cust => this.customer = cust)); this.subs.sink = this.obsService.subscribe(…);
}
ngOnDestroy() { this.subs.unsubscribe(); } }
Using SubSink :
Add multiple subscription objects to subsink and call its unsubscribe() function in ngOnDestroy()
https://github.com/wardbellisubsink
this way we do not need multiple names for different subscriptions, it puts all of them into single collection
using take or untill for unsubscribing observables
using take or untill for unsubscribing observables
Component Communication Summary
RxJS subjects provide a flexible way to communicate between components
BehaviorSubject returns the last emitted value to new subscribers
An event bus can be used for loosely coupled communication
An observable service exposes uses a subject to expose an observable
Unsubscribe from observables (pick a technique that works for you)
The need for State Management
if the app is very simple then we could use with services and there is no need for State Management
when the application is complex; and similar thing like updating customer object is being done at multiple places then it is better to use state management
State Management Goals
Single Source of Truth = so we are not updating state in multiple places and avoiding debugging nightmare
Predictable = we want things to be predictable
Immutable = you need to go to one place, if you need to change the state but the state itself never changes, we always create a new state
Track state changes = for better debugging; we could see before and after and see what action causes the state changes
Different types of states
Application State = application itself may have some state
it might have some urls passed to it
some security information or other types of things that are needed
Session State = unique to the user,
user preferreces
or settings for user
Entity State = actual data used in the application or entity state
like the customers, order, invoices what ever it might be that the application is working with and displaying and collecting data; as the end users are interacting with the application
State Management Options
Service = using services for state management
NgRx = using NgRx
ngrx-data = simplified version of NgRx; it is an extension that wraps NgRx functionality and makes it much easier to work with, with much code
Observable Store = this builds up on the observable service that we discussed with subjects; but adds statemanagement into the mix; it notifies different subscribers as the state chagnes
OTHER STATE MANAGEMENT OPTIONS- not discussed here
Akita
Ngxs
MobX
State Management with Angular Services
if we want to perform reusable calculation on the front end, validation, or may be talk to the server through http client. we would use angular service to do that
A service is typically a class with a narrow, well-defined purpose. It shoulc do something specific and Co it well.
https://angular.io/guide/architecture-services
for simple apps - services are good enough for managing state
State Management with Angular Services - simple way
Component 1 < – Service –> Component 2
Simplest approach
Inject Service where needed
Provide notifications using a subject (provides notification to the subscribers)
as we are working with components that need data, using services are great; as they provide one place to go, very good for maintenance, good for change request, reuse
State Management with Angular Services - Challenges
if we have a component interacting with multiple services and if a customer is updated in multiple place this might cause problems
we could mitigate this by having every service talk to Store which stores the session data; in this case the STORE is the centralized data location
we need to make sure that every developer is using the store and we are not mutating the store objects somewhere else
State Management with NgRx
NgRx Store provides reactive state management for Angular apps inspired by Redux. Unify the events in your application and derive state using RxJS. https://ngrx.io
NgRx
Redux + RxJS = NgRx
Single source of truth for state
Immutable data = data is not mutated in multiple locations
Provide consistency across a team
Diagnostic tool to watch store = it provides great debugging experience; it uses Redux tool, that lets you to watch store and see changes to your state, as it flows through out your application
Basic NgRx State Flow
Store = responsible for storing the state of our application; it could be application state, session state or entity state
in order to interact with the Store and the State, we are going to use actions
actions are going to be passed to Reducers
Reducer are translators, they take the action, act upon it and they are going to interact with the store state
we could get data back to the component through selectors$
FLOW= components sends an Actions
Actions goes to Reducers
Reduce is going to modify the state in the store
Then we get the data from selector
NgRx State Flow - General use case - where we talk to server
we might need to talk to the server to get data as well
if a Component needs customers info:
Component sends GetCusomer to Store Action
This action triggers an Effect (we have to write the code for it)
Effect integrates with the server and get the data from the server
Effect send the appropriate action that we now have the data
This goes from Action to the Reducers
Reducer updates the State Store
The selectors$ get the data back to the component
NgRx and the different features it providers satisfies all the goals for the State Management
it provides predictable pattern that everyone on the team can follow
it creates lot of complexity, as we are going to have lot of code in the application
if everyone on the team has good understanding of the pattern and different pieces involved in it then it is good option; if not things might get complicated
NgRx inAction
we need to install the below packages @ngrx/effects @ngrx/entity @ngrx/store @ngrx/store-devtools
we will find them in package.json
we are going to have Store folder, with actions, effects, reducers, services
when customer component needs data
it is going to inject our store and the store is going to have our entity state
we are also injecting the selectors, because as the store changes. we want to subscribe to that so that this component could get those changes and push them down to a child component (in this case, it is the customer list)
we are also monitoring when are the customers loading, that way we could show the spinner very easily
export class CustomersComponent implements OnInit { title = ‘Customers’;
customers$: Observable;
loadings: Observable;
constructor( private store: Store, private customerSelectors: CustomerSelectors)
{
this.customers$ = this.customerSelectors.customers$; this.loading$ = this.customerSelectors.loading$;
}
ngOnInit() {
this.getCustomers():
}
getCustomers()
{
this.store.dispatch(new CustomerAction.GetCustomers()):
}
we are going to call getCustomers()
this is going to dispatch an action to the store
getCustomers()
{
this.store.dispatch(new CustomerAction.GetCustomers()):
}
GetCustomer, it is a simple action that implements interface called Action
GetCustomerSuccess - when the success occurs
GetCustomerError = when error occured
export class GetCustomers implements Action { readonly type = GET CUSTOMERS; }
export class GetCustomersSuccess implements Action { readonly type = GET_CUSTOMERS_SUCCESS; constructor(public readonly payload: Customer()) {} }
export class GetCustomersError implements Action {
readonly type = GET_CUSTOMERS_ERROR; constructor(public readonly payload: any) {}
}
—
Customer Reducer:
export function reducer( state = initialState, action: CustomerActions.AllCustomerActions ): CustomerState {
switch (action.type) {
case CustomerActions.ADD_CUSTOMER:
{
return { …state, loading: true }
case CustomerActions.ADD_CUSTOMER_SUCCESS: { return
{
…state,
loading: false,
customers: (…state.customers, { …action.payload}]
case CustomerActions.ADD_CUSTOMER_ERROR:
{
return { …state, loading: false };
}
the reducer function takes the state and the action to perform
case CustomerAction.GET_CUSTOMERS:{
return
{…state, loading:true}
}
here reducer is going to take a state and make a new object and set the loading property to true
this is going to interact with the store; specifically the state in the store
Effect:
@Effect( getCustomers4: Observable = this.actions$ ,pipe( ofType(CustomerActions.GET_CUSTOMERS),
switchMap(() => toAction( this.customerDataServic.getCustomers(), CustomerActsons.GetCustomersSuccess, CustomerActsons.GetCustomersError
)
)
);
effect monitors for specific action when that ction occurs, it calls the service
constructor(private http: HttpClient) {}
getCustomers(): Observable
{
return this.http.get(‘${this.apiUrlBase)lcustomers’) .pipe( catchError(this.handleError()) );
this returns the data
then getCustomerSuccess action is going to be passed to the action;
the reducer updates the store
customer-selector:
this subscribes to the store
const getAllCustomers = createSelector(
getCustomerState,
(state:CustomerState) => state.customers
);
this is grabbed in the component
this.customers$=this.customerSelectors.customers$;
in customer; it is observable
customers$:Observable;
we are passing it to the child with input property:
<div>
</div>
using async pipe as it is observable
we always dispactch an action from the component reducer is going to get called effects may get called Store gets updated selectors return the data back
as long as everyone is dispatching from the component, everyone will have a consistent way to work with the components
Redux DevTools in chrome
allow to replay the state at different points in the application load process
we could see the payloads
and see different tasks that occured
ngrx-data
ngrx-data is an NgRx extension that offers a gentle introduction to ngrx/redux without the boilerplate.
https://github.com/ngrx
ngrx-data - it is a wrapper around NgRx
you do not need to write all teh boilerplate code for actions, effects, reducers and selectors;
it makes one of code for entity; like order, invoice
then we write service that acts as a gateway for the entity to get the data
we could customer ngrx data; if we need to get deeper into ngrx reducer or call to service
instead of dispatching to the store, we use ngrx-data service
component - will call the ngrx-data-service ; behind the scenes it will communicate with action, reducers, effect, selectors
it will update the state store
once that is one, we will subscribe to the observable
ngrx-data in action -Component
instead of injecting a store in the component, we inject the customer service
export class CustomersComponent implements OnInit { title = ‘Customers’;
customers$: Observable;
loading$: Observable;
constructor(private customerService: CustomerService)
{
this.loading$= this.customersService.loading$;
}
ngOnInit()
{
this.getCustomers();
}
getCustomers()
{
this.customers$=this.customersService.getAll();
}
getAll is out of the box
this is is like standard angular where you have injected the service in the constructor
ngrx-data in action - Service
import { Injectable from s@angular/core;
import { EntityCollectionServiceBase, EntityCollectionServiceElementsFactory from
import { Customer} from ‘../core/model/customer’;
@Injectable({ProvidedIn: 'root' }) export class CustomersService extends EntityCollectionServiceBase { constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) { super('Customer', serviceElementsFactory); } }
we are extending with Customer entity and calling the base class with super keyword and pass the name of the entity
there is no code to call the server through http client, it will do that for you out of the box as long as you configure entities
ngrx-data in action - model
ngrx-data in action - model
export class Customer{ id:number; name:string; city:string; orderTotal:number; }
Order model: export class Order{ id:number; customerId:number; orderItems:OrderItem[]; }
export class OrderItem{ id:number; productName:string; itemCost:number; }
ngrx-data in action - store - Entity MetaData.ts
import {EntityMetadataMap} from ‘ngrx-data’;
const entityMetadata: EntityMetadataMap={
Customer:{}, //this is one line of code per entity
Order:{}
};
const pluralNames= { };
export const entityConfig={
entityMetadata,
pluralNames
};
we register the entity with the entityConfig
ngrx-data in action - app-store.module.ts
const apiRoot = environment.apiUrlBase + '/'; const defaultDataServiceConfig: DefauttDataServiceConfig = { root: apiRoot, entityHttpResourceUrls: { Customer:{ entityResourceUrl: apiRoot + 'customers/', collectionResourceUrl: } Order:{ entityResourceUrl: apiRoot + 'orders/', collectionResourceUrl: apiRoot } ;
@NgModule({ imports: [ StoreModule.forRoot({}), EffectsModule.forRoot([]), NgrxDataModule.forRoot(entityConfig), environment.production ? [] : StoreDevtoolsModule.instrument() ], providers: [ { provide: DefaultDataServiceConfig, useValue: defaultDataServiceCon }) export class AppStoreModule
storeModule and EffectsModule are part of normal NgRx
but with NgrxDataModule - we are registering our entities
ngrx-data in action on orders component
Export class OrdersComponent implements Onlnit { orders$: Observable; toading$: Observable
ngrx-data in action - it has Redux Dev tools
ngrx-data in action - it has Redux Dev tools
Observable Store - state management
Observable Store provides a simple way to manage state in a front-end application while achieving many of the key goals offered by more complex state management options.
https://github.com/DanWahlin/Observable-Store
Observable Store Goals
Single source of truth State is read-only/immutable Provide state change notifications to any subscriber Track state change history Minimal amount of code required Works with any library/framework
Observable Store working
Component is going to call into angular service
this service is going to extend observable store; it is npm package
it is going to maintain state and get simple api for interacting with the api
you could call getState or SetState
it also tracks the state
as component calls in the service interacts with the store
if you want to interact with the server, you use http client;
There are no actions, reducers, effects
it is single store that multiple services could read and write from
the store is immutable, you are always getting a fresh copy when updates are made
observable store
observable service was a way to exchange data between components
observable store is like observable service with an api like history tracker, to track chagnes, get state and update state
observable Store in action
npm package @codewithdan/observable-store
@Injectable({
providedIn: ‘root’
})
export class CustomersService extends ObservableStore { apiUrl = 'api/customers'; constructor(private http: HttpClient) { super({ trackStateHistory: true }); //calling base class to set tracking }
getAll()
{
const state= this.getState();// comes from observable store
//pull from store cache
if(state&& state.customers) { console.log(this.stateHistory); return of(state.customers); }
//does not exist in store , so fetch the data else{ return this.fetchCustomers() .pipe( catchError(this.handleError) ); } }
private fetchCustomers(){ return this.http.get(this.apiurl) .pipe( map(customers=> { this.setstate({customers},CustomersStoreAction.GetCustomers); // it has type of action performed with Enum return customers; {), catchError(this.handleError) ); }
} it extends the observableStore and sets the StoreState on which entities to store --- export interface StoreState{ customers: Customer[]; customer:Customer; orders:Order[]; } // what ever you want to store we define here
in the component:
getcustomers()
{
this.customers$= this.customersService.getAll();
}
in the console ; it show the state properties; begin state and end state
Choosing a state Mangement option
use Services - with simple applications that are not complex
which do not have lot of local state; may be your application only talks to the server for everything it does
in that case you might not need to store lot of state, you may not need immutable nature of state management
Medium Complex apps - ngrx-data/Observable store fits well
it gives the power of ngrx with out introducting lot of code;
if you have multiple components that needs to be notified as something happens; both these options allow subscribing to the state
Medium to Complex app = if you understand Redux and overall pattern provided by NgRx
and okey to introducing quite a bit of boiler plate code in the app
everyone on the team should understand the pattern
if everyone ok with boilerplate code
if several team members copy and paste the code; then question if that is the way to go
State Management option
Measure Twice - cut once
try practicing before implementing
Function vs Pipes
Functions gets called mutiple times
Function calls made from a template are invoked every time a change occurs (no caching); it is always a fresh call
they are called lot more especially if you have other type of filters like text boxes, dropdown etc. they are causing properties to change in the component
{{ addTax(product.price) | currency }}
we could replace this with PIPE
A Pure Pipe returns the same result give the same inputs
Pure Pipe
A Pure Pipe returns the same result give the same inputs
Pipes are only called when the inputs are changed. other properties in the component might be changing but that will not impact
instead of function, we are going to create a custom pipe (addtax)
{{ product.price | addtax | currency }}
addtax is a function, but because the main functionality is in the transform function of the pipe, other things that are changing in the component are not going to cause the transform function to be called over and over again
when user types in a text box, or user selects a dropdown
Function and Pipes in action
Function and Pipes in action
{{ produt.name }}
{{ addTax(product.price) | currency }}
addTax(price:number){
console.log(‘addTax() function called’);
return price + (price* this.tax);
}
any change to a input element like textbox, as the page is changing, angular calls these methods multiple times
it might cause performance issues
if the inputs to the pipe transform function do not change, then it does not need to be recalled
import {Pipe, PipeTransform} from ‘@angular/core’;
@Pipe({
name: ‘addtax’
})
export class AddTaxPipe implements PipeTransform{ transform(price:number):number{ if(price) { return this.getTotalPrice(price); } return price; }
getTotalPrice(price:number) { console.log('addtax pipe called'); let total = price + (price *0.8); return total; } }
if the inputs to the binding in the pipe does not change then the pipe functions are not called multiple times
Using a Memo Decorator on pipes - cache result based on inputs passed into pipe transform()
it is an open source package
it enhances caching of a pipe’s transform() function when a primitive value is passed; like number or string
it will monitor the input value and see the output; the output value is cached; if it sees an input for the first time it calculates but if it sees it again, it knows the input and gets the cached output value
import memo from memo-decorator’;
@Pipe({name: 'addtaxmemo'}) export class AddTaxMemoPipe implements PipeTransform{ @memo() transform(value:any, args?:any):any{ //retun product.price+tax; }
}
{{ product.price | addtaxmemo | currency}}
you have to be cautious about it as it is caching in memory; if you have enormous amount of data
if there is less data, it helps in speeding up the calculations which are complex
it is better to do these calculation in the service or server side if possible, but sometimes you might not have the data you need so you might do it on the angular or front end
HttpClient and RxJS operators
May be you might need to talk to two different apis at the same time
like GetPerson - from personAPI- to get the ID and URL
then use the ID to GetAddress from AddressAPI to get the address
RxJs tap
tap is a RxJS pipeable operator that returns identical Observable as source Observable and can be used to perform side effect such as logging each values emitted by source Observable. tap is declared as following.
public tap(nextOrObserver: Observer | function, error: function, complete: function): Observable tap has three optional parameters.
nextOrObserver: A normal Observable object to perform side effect.
error: Callback for errors in source Observable.
complete: Callback for completion of the source.
Find the sample example. of(1, 2, 3, 4).pipe( tap(el => console.log("Process "+ el), err => console.error(err), () => console.log("Complete") ), filter(n => n % 2 === 0) ).subscribe(el => console.log("Even number: "+ el));
using tap
getCharacters() { return this.http.get(this.baseUrl + 'people') .pipe( tap(res => { console.log('Before getCharacters map'); }), map(res => { return res['results']; }), tap(res => { console.log('After getCharacters map'); }) ); }
forkJoin in rxjs
import { forkJoin, of, from } from ‘rxjs’;
getCharactersAndPlanets() { return forkJoin( this.getCharacters(), this.getPlanets() ) .pipe( map((res) => { return { characters: res[0], planets: res[1] }; }), catchError(error => of(error)) ); }
getCharacters() { return this.http.get(this.baseUrl + 'people') .pipe( tap(res => { console.log('Before getCharacters map'); }), map(res => { return res['results']; }), tap(res => { console.log('After getCharacters map'); }) ); }
getPlanets() { return this.http.get(this.baseUrl + 'planets') .pipe( tap(res => { console.log('Before getPlanets map'); }), map(res => { return res['results']; }), tap(res => { console.log('After getPlanets map'); }) ); }
if we want to call both the observables at the same time
in the component:
charactersAndPlanets: { characters: any[], planets: any[]};
// Get both characters and planets at same time
// Uses forkJoin
this.dataService.getCharactersAndPlanets()
.subscribe(data => this.charactersAndPlanets = data);
Switch Map - when we have the observable, when we know that we need to finish it with some updated information from another observable
getCharactersAndHomeworlds() { return this.http.get(this.baseUrl + 'people') .pipe( switchMap(res => { // convert array to observable return from(res['results']); }), // concatMap((person: any) => { mergeMap((person: any) => { return this.http.get(person['homeworld']) .pipe( map(hw => { person['homeworld'] = hw; return person; }) ); }), toArray() ); }
we are getting the people and updating the homeworld with another call
in the component: characterWithHomeworld$: Observable; // Get character and its homeworld // Uses switchMap this.characterWithHomeworld$ = this.dataService.getCharacterAndHomeworld();
Here we are getting the observable instead of mapping the data to the property in the component: and using async in the html
<h2>People and Homeworlds</h2> <ul> <li> <span>{{ char.name }} ({{ char.homeworld.name }})</span> </li> </ul>
MergeMap
get all the characters and their home worlds
getCharactersAndHomeworlds() { return this.http.get(this.baseUrl + 'people') //we are getting array .pipe( switchMap(res => { // convert array to observable return from(res['results']); //converting array to observable }), // concatMap((person: any) => { mergeMap((person: any) => { return this.http.get(person['homeworld']) .pipe( map(hw => { person['homeworld'] = hw; return person; }) ); }), toArray() //convertting the whole result set to array ); }
in component:
charactersWithHomeworld$: Observable;
this.charactersWithHomeworld$ = this.dataService.getCharactersAndHomeworlds();
in html: <h2>People and Homeworlds</h2> <ul> <li> <span>{{ char.name }} ({{ char.homeworld.name }})</span> </li> </ul>
Note that MergeMap wont gurantee that the order of the people is preserved as they are returned. Use concatMap i f you want the original order of people perserved
it is more like us writing loop of all people, but rxjs is doing it for us
we are calling toArray(), because we do not want each of the results to be pushed to the subscribers one by one; instead all to be passed in one go
forkJoin switchMap, MergeMap
these help in making parallel calls to the api instead of serial calls
Security - Cross-Origin Resource sharing (CORS)
our angular app may call app in different domain or same domain with different port; which is cross origin
CORS allows a browser to call a different domain or port
Enable on the server as needed, it depends on server side technology used; it is not done on angular
Limit allowed domains, headers and methods; to what is needed
if it is public api you might say * for allowing
Security - Cross site Request Forgery (CRSF)
someone sending request to your website
but
Enable CSRF on the server if using cookie authentication
Angular will read a token from a cookie set by the server and add it to the request headers
only the same domain can set this request header
bad site can not set the request header;
when the request is made to the server, if the request head was not there it will be blocked
in angular you could change the cookie if needed
Change the cookie/header name as appropriate for your server
https://angular.io/guide/http#security-xsrf-protection
server will validate the header value
Note that a simple POST request can be used for CSRF attack
Security - Route guard
Route guard - used to direct the user to login screen or some other page, if they do not have proper security credentials as dictated by the server
may be they are not in the proper role or group
there is no such thing as security in the browser, the server is ultimate source of security for routes, api
Define route guards needed by application based on user or group/role
Keep in mind that route guards do not “secure” an application
Rely on the server to secure data, API etc
Security - Sensitive Data
Anyone can access the browser developer tools to view variables, local/session storage, cookies etc
Do not store sensitive data like secrets used to call an api, keys that are important, passwords in the browser
when you are calling a 3rd party api and you are calling directly from angular you need to pass the key and the secret inorder to call it
some people will pass that secret to angular, store it and angular will have it locally as it calls your api. anybody wants could get that secret
if an api requires a secret to be passed. consider calling it through a middle-man service that you own
which has the secrets
Use JWT token where possible for server authentication (set appropriate TTL expiration for token). we do not need a cookie containers; we could use this for authentication and authorization
additional:
authentication on Server
authorization like roles/groups
HTTPS - we would want from angular to api
what if api calls another api, if we want end to end or point to point calls
HTTP Interceptors
HTTP Interceptors